模态框切换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
相关产品推荐
相关产品推荐

