So vermeiden Sie „Zu viele erneute Renderings. React begrenzt die Anzahl der Renderings, um Endlosschleifen zu verhindern.'
P粉009186469
P粉009186469 2024-04-01 19:21:53
0
1
330

Ich verwende React TypeScript, Redux Toolkit und Material UI. Ich erhalte diese Fehlermeldung beim Aufruf der API:

Fehler: Zu viele erneute Renderings. React begrenzt die Anzahl der Renderings, um Endlosschleifen zu verhindern. in renderWithHooks (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:12178:23) In mountInminatedComponent (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:14921:21) Bei beginWork (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:15902:22)....

Ich stelle meinen Code unten zur Verfügung:

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;

Ich habe versucht, die Abhängigkeit in useEffect zu entfernen. Aber der Fehler besteht immer noch.

P粉009186469
P粉009186469

Antworte allen(1)
P粉594941301

问题

这里的问题是将 React 状态更新排入 React 组件生命周期外部,这是一种无意的副作用。每当 EditMenuPermission 组件呈现时都会调用此代码,如果 role.menus 为真,则会将状态更新排入队列并触发组件重新呈现。这是您看到的渲染循环。

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

解决方案

roleMenus 状态更新移至组件生命周期中。

简单的解决方案

一种简单的方法是使用 useEffect 挂钩将 roleMenus 状态同步到当前 role.menus 值。

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

改进的解决方案1

这可行,但通常被认为是将派生的“状态”存储到 React 状态中的 React 反模式。当前的roleMenus值可以很容易地从当前的role.menus值计算出来。您应该记住,如果您发现自己编写了 useState/useEffect 耦合,那么大约 100% 的情况下,您应该使用 useMemo 钩子代替。

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

改进的解决方案2

如果这是您经常从 Redux 选择和计算的内容,我建议考虑将逻辑移至选择器函数中。

示例:

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[];;

进一步改进的建议

更好的是,在更新 Redux 状态时,只需 JSON.parse 切片缩减器函数中的角色数据,这样每次更新状态时只需执行一次计算,而不是每次读取状态时进行计算。

Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!