Cara untuk mengelakkan "Terlalu banyak pemaparan semula. React mengehadkan bilangan pemaparan untuk mengelakkan gelung tak terhingga."
P粉009186469
P粉009186469 2024-04-01 19:21:53
0
1
356

Saya menggunakan React TypeScript, kit alat Redux dan UI Bahan. Saya mendapat ralat ini apabila memanggil API:

Ralat: Terlalu banyak paparan semula. React mengehadkan bilangan pemaparan untuk mengelakkan gelung tak terhingga. dalam renderWithHooks (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:12178:23) Dalam mountInminatedComponent (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:14921:21) Pada beginWork (http://127.0.0.1:5173/node_modules/.vite/deps/chunk-QJV3R4PZ.js?v=8a99eba5:15902:22)....

Saya berikan kod saya di bawah:

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;

Saya cuba mengalih keluar kebergantungan dalam useEffect. Tetapi kesilapan itu masih wujud.

P粉009186469
P粉009186469

membalas semua(1)
P粉594941301

Soalan

Masalahnya di sini ialah beratur kemas kini keadaan React di luar kitaran hayat komponen React merupakan kesan sampingan yang tidak disengajakan. Apabila EditMenuPermission 组件呈现时都会调用此代码,如果 role.menus adalah benar, kemas kini keadaan akan dimasukkan ke dalam baris gilir dan komponen dicetuskan untuk dipaparkan semula. Ini ialah gelung render yang anda lihat.

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

Penyelesaian

Alihkan roleMenus kemas kini status ke dalam kitaran hayat komponen.

Penyelesaian mudah

Cara mudah ialah menggunakan cangkuk useEffect untuk menyegerakkan keadaan useEffect 挂钩将 roleMenus 状态同步到当前 role.menus kepada nilai role.menus semasa.

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

Penyelesaian yang lebih baik 1

Ini berfungsi, tetapi secara amnya dianggap sebagai anti-corak React untuk menyimpan "keadaan" terbitan ke dalam keadaan React. Nilai roleMenus值可以很容易地从当前的role.menus值计算出来。您应该记住,如果您发现自己编写了 useState/useEffect 耦合,那么大约 100% 的情况下,您应该使用 useMemo semasa boleh dikira dengan mudah daripada nilai role.menus semasa. Anda harus ingat bahawa jika anda mendapati diri anda menulis gandingan useState/useEffect, maka kira-kira 100% masa anda harus menggunakan useMemo cangkuk ganti.

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

Penyelesaian yang lebih baik 2

Jika ini adalah sesuatu yang anda kerap pilih dan kira daripada Redux, saya syorkan anda mempertimbangkan untuk memindahkan logik ke dalam fungsi pemilih.

Contoh:

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

Cadangan untuk penambahbaikan selanjutnya

Lebih baik lagi, apabila mengemas kini keadaan Redux, hanya JSON.parse data peranan dalam fungsi pengurangan kepingan, jadi anda hanya perlu melakukan satu pengiraan setiap kali keadaan dikemas kini, bukannya setiap kali keadaan dibaca.

Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan