J'utilise React TypeScript, la boîte à outils Redux et Material UI. J'obtiens cette erreur lors de l'appel de l'API :
Erreur : trop de nouveaux rendus. React limite le nombre de rendus pour éviter les boucles infinies. dans renderWithHooks (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:12178:23) Dans mountInratedComponent (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:14921:21) AubebeWork (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:15902:22)....
Je fournis mon code ci-dessous :
EditMenuPermission.tsx
//EditMenuPermission.tsx //other imports /* ++++ Redux Imports ++++ */ import { useDispatch, useSelector } from "react-redux"; import { AppDispatch, RootState } from "src/redux"; import { roleActions } from "../roles/RolesActions"; /* ---- Redux Imports ---- */ const EditMenuPermission = () => { const { id } = useParams(); const [selected, setSelected] = useState<RoleMenuItem[]>( [] as RoleMenuItem[] ); const [selectedIds, setSelectedIds] = useState<number[]>([] as number[]); const role = useSelector((state: RootState) => state.roles.selected) as Role; const [roleMenus, setRoleMenus] = useState<RoleMenuItem[]>([]); if (role?.menus) { try { const parsedMenus = JSON.parse(role.menus) as RoleMenuItem[]; setRoleMenus(parsedMenus); } catch (error) { console.error("Error parsing role menus:", error); } } const dispatch = useDispatch<AppDispatch>(); useEffect(() => { dispatch(roleActions.findOne(id as unknown as number)); }, [dispatch, id, role?.id]); console.log("previousMenus:", roleMenus, "selected:", selected); const handleCreatePayload = async () => { const updatedMenus = [...roleMenus]; selected.forEach((selectedItem) => { const existingItemIndex = updatedMenus.findIndex( (menu) => menu.id === selectedItem.id ); if (existingItemIndex !== -1) { updatedMenus[existingItemIndex] = selectedItem; } else { updatedMenus.push(selectedItem); } }); setRoleMenus(updatedMenus); const payload = { name: role.name, is_active: true, is_deleted: false, menus: JSON.stringify(updatedMenus), }; console.log("updated Menus:", updatedMenus); const updateRole = await dispatch(roleActions.update(role.id, payload)); console.log(updateRole); }; return ( <Box> <AdminTitleContainer> <AdminTitle variant="h5">Role Permission</AdminTitle> </AdminTitleContainer> <Grid container spacing={2}> <Grid item xs={9}> <Box> <RoleMenuTrees selected={selected} setSelected={setSelected} selectedIds={selectedIds} setSelectedIds={setSelectedIds} roleMenus={roleMenus} /> </Box> </Grid> <Grid item xs={3}> <Button variant="contained" color="primary" startIcon={<AddCircle />} onClick={handleCreatePayload} sx={{ position: "fixed" }} > Save </Button> </Grid> </Grid> </Box> ); }; export default EditMenuPermission;
RoleMenuTrees.tsx
//other imports /* ++++ Redux Imports ++++ */ import { useDispatch, useSelector } from "react-redux"; import { AppDispatch, RootState } from "src/redux"; import { roleActions } from "src/features/admin/roles/RolesActions"; /* ---- Redux Imports ---- */ import { useEffect } from "react"; import { useParams } from "react-router-dom"; import { useRoleMenuTree } from "src/hooks/useMenuTree"; import { SingleRoleMenuDTO } from "src/features/admin/roles/RolesDTO"; import { menuActions } from "src/features/admin/menu/MenuActions"; import { AllMenu, Permission, PermissionType, RoleMenuItem, SingleRole, } from "../../RoleDTO"; type RoleMenuTreesProp = { selected: RoleMenuItem[]; setSelected: React.Dispatch<React.SetStateAction<RoleMenuItem[]>>; selectedIds: number[]; setSelectedIds: React.Dispatch<React.SetStateAction<number[]>>; roleMenus: RoleMenuItem[]; }; const RoleMenuTrees = ({ selected, setSelected, selectedIds, setSelectedIds, roleMenus, }: RoleMenuTreesProp) => { const dispatch = useDispatch<AppDispatch>(); const { id } = useParams(); const roleMenusJSON = useSelector( (state: RootState) => state.roles.selected as SingleRole )?.menus; const allMenus = useSelector( (state: RootState) => state.menus.list ) as AllMenu[]; useEffect(() => { dispatch(menuActions.getList()); }, [dispatch, id]); /*++++ merging roleMenus + allMenus starts +++++*/ const mergedMenus = allMenus?.map((menu) => { const matchingMenu = roleMenus.find( (roleMenu: RoleMenuItem) => roleMenu.id === menu.id ); if (matchingMenu) { const { permissions: _, ...rest } = { ...menu, ...matchingMenu }; return rest; } else { const permissions = JSON.parse(menu.permissions) as Permission[]; const permissionType = {} as PermissionType; permissions?.forEach((permission) => { const { key } = permission; permissionType[key] = false; }); const { permissions: _, ...rest } = { ...menu, permission_type: permissions, ...permissionType, }; return rest; } }); console.log("mergedMenus:", mergedMenus); /*---- merging roleMenus + allMenus ends ----*/ const createRoleMenuTree = useRoleMenuTree( mergedMenus as unknown as SingleRoleMenuDTO[] ); const tree = createRoleMenuTree.tree; const mapMenu = createRoleMenuTree.mapMenu; return ( <Box> <Box sx={{ backgroundColor: "#fafafa" }}> {/*++++ Menu List starts ++++*/} <TreeView className="TreeView" defaultExpandIcon={ <ChevronRightIcon sx={{ fontSize: "1.5rem !important" }} /> } defaultCollapseIcon={ <ExpandMoreIcon sx={{ fontSize: "1.5rem !important" }} /> } > {tree?.map((data) => ( <Box key={data.id}> <RoleMenuTree data={data as unknown as RoleMenuItem} selected={selected} setSelected={setSelected} selectedIds={selectedIds} setSelectedIds={setSelectedIds} mapMenu={mapMenu} /> </Box> ))} </TreeView> {/*---- Menu List ends ----*/} </Box> </Box> ); }; export default RoleMenuTrees;
J'ai essayé de supprimer la dépendance dans useEffect. Mais l'erreur existe toujours.
Question
Le problème ici est que la mise en file d'attente des mises à jour de l'état de React en dehors du cycle de vie des composants React est un effet secondaire involontaire. Chaque fois que
EditMenuPermission
组件呈现时都会调用此代码,如果role.menus
est vrai, une mise à jour d'état est mise en file d'attente et le composant est déclenché pour un nouveau rendu. C'est la boucle de rendu que vous voyez.Solution
Déplacez
roleMenus
les mises à jour de statut dans le cycle de vie des composants.Solution facile
Un moyen simple consiste à utiliser le hook
useEffect
pour synchroniser l'étatuseEffect
挂钩将roleMenus
状态同步到当前role.menus
avec la valeurrole.menus
actuelle.Solution améliorée 1
Cela fonctionne, mais est généralement considéré comme un anti-modèle React de stockage de « l'état » dérivé dans l'état React. La valeur actuelle de
roleMenus
值可以很容易地从当前的role.menus
值计算出来。您应该记住,如果您发现自己编写了useState
/useEffect
耦合,那么大约 100% 的情况下,您应该使用useMemo
peut être facilement calculée à partir de la valeur actuelle derole.menus
. Vous devez garder à l'esprit que si vous vous retrouvez à écrire un couplageuseState
/useEffect
, alors environ 100 % du temps, vous devez utiliser leuseMemo
crochet remplacer.Solution améliorée 2
S'il s'agit de quelque chose que vous sélectionnez et calculez fréquemment à partir de Redux, je vous recommande d'envisager de déplacer la logique dans une fonction de sélection.
Exemple :
Suggestions d'améliorations supplémentaires
Mieux encore, lors de la mise à jour de l'état Redux, analysez simplement en JSON les données de rôle dans la fonction de réduction de tranche, vous n'avez donc qu'à effectuer un calcul à chaque fois que l'état est mis à jour, plutôt qu'à chaque fois que l'état est lu.