You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

模态框切换Topic时PermissionCheckbox组件不重渲染的问题

问题描述

使用MantineUI开发用户管理组件时遇到如下问题:

  • 用户以表格行形式展示,点击每行的编辑图标会打开带编辑选项的模态框
  • 角色、账号状态下拉框功能正常,但切换ComboBoxTopic(主题选择下拉框)后,PermissionCheckbox(权限复选框组件)无法更新显示对应主题的权限,仅能展示模态框打开前选中的主题权限,关闭并重新打开模态框才会显示正确权限
  • 尝试在selected变化时强制更新复选框无效,甚至PermissionCheckbox中的useEffect都未触发

相关代码

主组件(User.tsx)

import { Text, Badge, Group, Table, ActionIcon, rem, Flex,} from "@mantine/core";
import { modals } from "@mantine/modals";
import { IconPencil } from "@tabler/icons-react";
import { ComboBox } from "./ComboBox";
import { useState } from "react";
import { UpdateUserRequest } from "../../types/types";
import { ComboBoxTopic } from "./ComboBoxTopic";
import userService from "../../services/user.service";
import { notifications } from "@mantine/notifications";
import { PermissionCheckbox } from "./PermissionsCheckbox";

interface Props {
  user: any;
  topics: any;
  reload: any;
}

const statusColors: Record<string, string> = {
  REQUESTED: "orange",
  APPROVED: "green",
  REJECTED: "red",
};

export function User({ user, topics, reload }: Props) {
  const updatedUser: UpdateUserRequest = JSON.parse(JSON.stringify(user));
  const [selectedTopic, setSelectedTopic] = useState<string>(topics[0].name);

  const openModal = () =>
    modals.openConfirmModal({
      title: `Edit user '${user.username}'`,
      children: (
        <Flex direction="column" align="center" gap="xl">
          <Flex direction="row" align="center" justify="space-evenly" w="100%">
            <Flex direction="column" align="center">
              Role
              <ComboBox
                selected={user.role}
                type="ROLE"
                updatedUser={updatedUser}
              />
            </Flex>
            <Flex direction="column" align="center">
              Status
              <ComboBox
                selected={user.status}
                type="STATUS"
                updatedUser={updatedUser}
              />
            </Flex>
          </Flex>
          <Flex direction="column" align="center" gap="lg">
            <Flex direction="column" align="center">
              Permissions for Topic
              <ComboBoxTopic
                selected={selectedTopic}
                topics={topics}
                updatedUser={updatedUser}
                setSelected={setSelectedTopic}
              />
            </Flex>
            <PermissionCheckbox
              key={selectedTopic}
              selected={selectedTopic}
              user={updatedUser}
              topics={topics}
            />
          </Flex>
        </Flex>
      ),
      centered: true,
      labels: { confirm: "Save", cancel: "Cancel" },
      onConfirm: () => onEditSave(),
    });

  const onEditSave = () => {
    userService
      .updateUser(user.id, updatedUser)
      .then(() => {
        reload();
        notifications.show({
          title: "Update Succeed",
          message: "User was successfully updated",
        });
      })
      .catch(() => {
        notifications.show({
          title: "Update Failed",
          message: "User could not be updated",
        });
      });
  };

  return (
    <Table.Tr key={user.id}>
      <Table.Td>
        <Group gap="sm">
          <Text fz="sm" fw={500}>
            {user.username}
          </Text>
        </Group>
      </Table.Td>
      <Table.Td>
        <Text fz="sm">{user.role}</Text>
      </Table.Td>
      <Table.Td>
        <Badge color={statusColors[user.status]} variant="light">
          {user.status}
        </Badge>
      </Table.Td>
      <Table.Td>
        <Text fz="sm">{user.email}</Text>
      </Table.Td>
      <Table.Td>
        <Group gap={0} justify="flex-end">
          <ActionIcon variant="subtle" color="gray" onClick={openModal}>
            <IconPencil
              style={{ width: rem(24), height: rem(24) }}
              stroke={1.5}
            />
          </ActionIcon>
        </Group>
      </Table.Td>
    </Table.Tr>
  );
}

ComboBoxTopic组件

import { useState } from "react";
import { CheckIcon, Combobox, Group, Input, InputBase, useCombobox } from "@mantine/core";
import { UpdateUserRequest } from "../../types/types";

interface Props {
  selected: string;
  topics?: any;
  updatedUser: UpdateUserRequest;
  setSelected: any;
}

export function ComboBoxTopic({ selected, topics, setSelected }: Props) {
  const combobox = useCombobox({
    onDropdownClose: () => combobox.resetSelectedOption(),
    onDropdownOpen: (eventSource) => {
      if (eventSource === "keyboard") {
        combobox.selectActiveOption();
      } else {
        combobox.updateSelectedOptionIndex("active");
      }
    },
  });

  const [value, setValue] = useState<string>(selected);

  const options = topics.map((topic: any) => (
    <Combobox.Option
      value={topic.name}
      key={topic.name}
      active={topic.name === value}
    >
      <Group gap="xs">
        {topic.name === value && <CheckIcon size={12} />}
        <span>{topic.name}</span>
      </Group>
    </Combobox.Option>
  ));

  return (
    <Combobox
      store={combobox}
      width="40%"
      resetSelectionOnOptionHover
      withinPortal={false}
      onOptionSubmit={(val) => {
        setSelected(val);
        setValue(val);
        combobox.updateSelectedOptionIndex("active");
      }}
    >
      <Combobox.Target targetType="button">
        <InputBase
          component="button"
          type="button"
          pointer
          rightSection={<Combobox.Chevron />}
          rightSectionPointerEvents="none"
          onClick={() => combobox.toggleDropdown()}
        >
          {value || <Input.Placeholder>Pick value</Input.Placeholder>}
        </InputBase>
      </Combobox.Target>

      <Combobox.Dropdown>
        <Combobox.Options>{options}</Combobox.Options>
      </Combobox.Dropdown>
    </Combobox>
  );
}

PermissionCheckbox组件

import { Checkbox, Flex } from "@mantine/core";
import { UpdateUserRequest } from "../../types/types";
import { useEffect, useState } from "react";

interface Props {
  selected: string;
  topics: any;
  user: UpdateUserRequest;
}

export function PermissionCheckbox({ selected, topics, user }: Props) {
  const [topicPermissions, setTopicPermissions] = useState<any>();

  useEffect(() => {
    const topic = topics.find((t: any) => t.name === selected);
    setTopicPermissions(
      user.permissions.find((p: any) => p.idTopic === topic.id)
    );
  }, [selected]);

  return (
    <>
      {topicPermissions && (
        <Flex direction="column" gap="md" justify="center" align="flex-start">
          <Checkbox
            label="Write"
            checked={topicPermissions.write}
            onChange={(event) => {
              if (topicPermissions)
                setTopicPermissions((prevTopicPermissions: any) => ({
                  ...prevTopicPermissions,
                  delete: event.currentTarget.checked,
                }));
            }}
          />
          <Checkbox
            label="Update"
            checked={topicPermissions.update}
            onChange={(event) => {
              if (topicPermissions)
                setTopicPermissions((prevTopicPermissions: any) => ({
                  ...prevTopicPermissions,
                  update: event.currentTarget.checked,
                }));
            }}
          />
          <Checkbox
            label="Delete"
            checked={topicPermissions.delete}
            onChange={(event) => {
              if (topicPermissions)
                setTopicPermissions((prevTopicPermissions: any) => ({
                  ...prevTopicPermissions,
                  delete: event.currentTarget.checked,
                }));
            }}
          />
        </Flex>
      )}
    </>
  );
}

解决方案

1. 修复Modal内容不响应状态变化的核心问题

Mantine的modals.openConfirmModal接收的children是静态React节点,会在调用时生成快照,后续外部状态变化无法触发内容重渲染。需要将children改为函数形式,让Modal每次渲染时获取最新状态:

const openModal = () =>
  modals.open({
    title: `Edit user '${user.username}'`,
    // 将children改为函数,确保每次渲染获取最新的selectedTopic
    children: () => (
      <Flex direction="column" align="center" gap="xl">
        <Flex direction="row" align="center" justify="space-evenly" w="100%">
          <Flex direction="column" align="center">
            Role
            <ComboBox
              selected={user.role}
              type="ROLE"
              updatedUser={updatedUser}
            />
          </Flex>
          <Flex direction="column" align="center">
            Status
            <ComboBox
              selected={user.status}
              type="STATUS"
              updatedUser={updatedUser}
            />
          </Flex>
        </Flex>
        <Flex direction="column" align="center" gap="lg">
          <Flex direction="column" align="center">
            Permissions for Topic
            <ComboBoxTopic
              selected={selectedTopic}
              topics={topics}
              updatedUser={updatedUser}
              setSelected={setSelectedTopic}
            />
          </Flex>
          <PermissionCheckbox
            key={selectedTopic}
            selected={selectedTopic}
            user={updatedUser}
            topics={topics}
          />
        </Flex>
      </Flex>
    ),
    centered: true,
    labels: { confirm: "Save", cancel: "Cancel" },
    onConfirm: () => onEditSave(),
    // 指定为确认型模态框
    modal: "confirm",
  });

2. 补全PermissionCheckbox的useEffect依赖

useEffect中用到了topics和user,但之前的依赖数组只包含selected,导致状态变化时无法触发更新,同时增加空值判断避免报错:

useEffect(() => {
  const topic = topics.find((t: any) => t.name === selected);
  if (!topic) return; // 空值判断,防止topic不存在时报错
  setTopicPermissions(
    user.permissions.find((p: any) => p.idTopic === topic.id)
  );
}, [selected, topics, user]); // 补充完整依赖

3. 修复PermissionCheckbox的Write复选框逻辑错误

Write复选框的onChange错误修改了delete属性,应改为write:

<Checkbox
  label="Write"
  checked={topicPermissions.write}
  onChange={(event) => {
    if (topicPermissions)
      setTopicPermissions((prevTopicPermissions: any) => ({
        ...prevTopicPermissions,
        write: event.currentTarget.checked, // 修正为write属性
      }));
  }}
/>

4. 修复ComboBoxTopic的useCombobox导入缺失

在ComboBoxTopic组件中补充useCombobox的导入:

import { CheckIcon, Combobox, Group, Input, InputBase, useCombobox } from "@mantine/core";

5. 优化updatedUser的状态管理(可选)

将updatedUser改为React state,确保状态变化可追踪、更可控:

// 替换原来的直接赋值
const [updatedUser, setUpdatedUser] = useState<UpdateUserRequest>(() => 
  JSON.parse(JSON.stringify(user))
);

内容的提问来源于stack exchange,提问作者qu4lizz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 22:00:56