Maison  >  Questions et réponses  >  le corps du texte

Comment éviter « Trop de nouveaux rendus. React limite le nombre de rendus pour éviter les boucles infinies. »

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.

P粉009186469P粉009186469172 Il y a quelques jours269

répondre à tous(1)je répondrai

  • P粉594941301

    P粉5949413012024-04-02 13:20:08

    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.

    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);
      }
    }
    

    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'état useEffect 挂钩将 roleMenus 状态同步到当前 role.menus avec la valeur role.menus actuelle.

    const role = useSelector((state: RootState) => state.roles.selected) as Role;
    const [roleMenus, setRoleMenus] = useState<RoleMenuItem[]>([]);
    
    useEffect(() => {
      if (role?.menus) {
        try {
          const parsedMenus = JSON.parse(role.menus) as RoleMenuItem[];
          setRoleMenus(parsedMenus);
        } catch (error) {
          console.error("Error parsing role menus:", error);
        }
      }
    }, [role?.menus]);
    

    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 de role.menus. Vous devez garder à l'esprit que si vous vous retrouvez à écrire un couplage useState/useEffect, alors environ 100 % du temps, vous devez utiliser le useMemo crochet remplacer.

    const role = useSelector((state: RootState) => state.roles.selected) as Role;
    
    const roleMenus = useMemo<RoleMenuItem[]>(() => {
      try {
        return JSON.parse(role.menus) as RoleMenuItem[];
      } catch (error) {
        console.error("Error parsing role menus:", error);
        return [];
      }
    }, [role?.menus]);
    

    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 :

    const selectRoleMenus = (state: RootState) => {
      const role = state.roles.selected;
    
      try {
        return JSON.parse(role.menus) as RoleMenuItem[];
      } catch (error) {
        console.error("Error parsing role menus:", error);
        return [];
      }
    };
    
    const role = useSelector((state: RootState) => state.roles.selected) as Role;
    const roleMenus = useSelector(selectRoleMenus) as RoleMenuItem[];;
    

    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.

    répondre
    0
  • Annulerrépondre