StupidGuy
StupidGuy

Reputation: 3

ReactJS Hook - How to re-render other component

I got three component here. MainComponent is including with other two. And what I want to do is, changes in select input in WebHeaderComponent would refresh sidemenucomponent layout. But error prompted when i try to do so.

MainComponent
  -> WebHeaderComponent
  -> SideMenuComponent

MainComponent.tsx

const { Content } = Layout;

const MainComopnent: FunctionComponent = () => {
  const [collapsed, setCollapsed] = useState(false);
  const [menuList, setMenuList] = useState([]);
  const services = useContext(ServicesContext);
  const { MenuService } = services;
  const { AuthenticationService } = services;

  useEffect(() => {
    const requestOption = {
      method: "post",
      body: JSON.stringify({
        userCode: AuthenticationService.getUserModel().userCode, 
        roleId: AuthenticationService.getUserModel().roleId
      })
    }

    MenuApi.getSideMenu(requestOption).then((result) => {
      console.log(result);
      const temp = MenuService.groupMenu(result);
      setMenuList(temp);
    });
  }, []);
  const value = { collapsed, setCollapsed };
  
  return (
    <SideMenuCollapsedContext.Provider value={value}>
      <Layout className="layout">
        <SideMenuComponent menuList={MenuService.getGroupMenu()}></SideMenuComponent>
        <Layout className="site-layout">
          <WebHeaderComponent></WebHeaderComponent>
          <Content
            className="site-layout-background"
            style={{
              margin: "24px 16px",
              padding: 24,
              minHeight: 280,
              overflowY: "scroll",
            }}
          >
            <AppRoutes />
          </Content>
        </Layout>
      </Layout>
    </SideMenuCollapsedContext.Provider>
  );
};
export default MainComopnent;

WebHeaderComponent.tsx

const { Header } = Layout;

const WebHeaderComponent  = () => {
  const services = useContext(ServicesContext);
  const { MenuService } = services;
  const { AuthenticationService } = services;
  const { UserInfoService } = services;
  const [userRoleList, setUserRoleList] =  useState<Array<any>>([]);
  const {collapsed, setCollapsed} = useContext(SideMenuCollapsedContext);

  const toggle = () => {
    setCollapsed(!collapsed);
  };

  useEffect(() => {
    const data = { userId: AuthenticationService.getUserModel().userId};
    SysUserApi.searchSysUserRole(data).then((res) => {
      setUserRoleList(res)
    })
  },[]);  

  const handleRoleChange = (selectedRoleId) => {
    console.log('before: ' + selectedRoleId);
    AuthenticationService.setRoleId(selectedRoleId);
    console.log('after: ' + AuthenticationService.getUserModel().roleId)
  }

  return (
    <Header className="site-layout-background" style={{ padding: 0 }}>
      {React.createElement(
        collapsed ? MenuUnfoldOutlined : MenuFoldOutlined,
        {
          className: "trigger",
          style: { paddingLeft: "10px", fontSize: "20px" },
          onClick: toggle,
        }
      )}

      <div
        style={{
          float: "right",
          paddingRight: "20px",
          display: "flex",
          alignItems: "center",
        }}
      >
          <div><span>用戶:{AuthenticationService.getUserModel().userName}</span></div>
          <div style={{ paddingLeft: "10px" }}>
            <span>公司-角色:</span>
            <Select 
              style={{ width: 120 }} 
              defaultValue={AuthenticationService.getUserModel().roleId}
              onChange={handleRoleChange}
            >
              {userRoleList.map(item => (
                <option
                  key={item.roleId}
                  value={item.roleId}
                >
                  {item.roleName}
                </option>
              ))}
            </Select>
          </div>
        <div style={{ paddingLeft: "10px" }}>
          <Button type="primary"> Sign Out </Button>
        </div>
      </div>
    </Header>
  );
}

export default WebHeaderComponent;

SideMenuComponent.tsx

const { Sider } = Layout;
const { SubMenu } = Menu;

const createSubMenus = (menuList): any => {
  const menus: any = [];
  for (const subMenuItem of menuList) {
    if (subMenuItem.children && subMenuItem.children.length > 0) {
      menus.push(
        <SubMenu
          key={subMenuItem.funcId}
          title={
            <span>
              <MailOutlined />
              <span className="fontWt">{subMenuItem.funcName}</span>
            </span>
          }
        >
          {createSubMenus(subMenuItem.children)}
        </SubMenu>
      );
    } else {
      menus.push(
        <Menu.Item key={subMenuItem.funcId + subMenuItem.parentId}>
          {subMenuItem.funcName}
          <Link to={"/app" + subMenuItem.funcUrl} replace></Link>
        </Menu.Item>
      );
    }
  }
  return menus;
};

const SideMenuComponent = ({ menuList }) => {
  const value = useContext(SideMenuCollapsedContext);
  const [subMenuList, setSubMenuList] = useState([]);
  const { collapsed, setCollapsed } = value;

  useEffect(() => {
    setSubMenuList(createSubMenus(menuList));
  }, [menuList]);

  const handleCollapsed = (collapsed) => {
    setCollapsed(collapsed);
  };

  return (
    <Sider collapsible collapsed={collapsed} onCollapse={handleCollapsed}>
      <div className="logo" />
      <Menu theme="dark" mode="inline" defaultSelectedKeys={["1"]}>
        {/* <Menu.Item key="1" icon={<UserOutlined />}>
          <Link to="/app/producttomms">Share Product to MMS</Link>
        </Menu.Item>
        <Menu.Item key="2" icon={<UserOutlined />}>
          <Link to="/app/paymentorderanalyzer">Payment Order Analyzer</Link>
        </Menu.Item>
        <Menu.Item key="3" icon={<UserOutlined />}>
          <Link to="/app/masterdatainitializer">Master Data Initializer</Link>
        </Menu.Item> */}
        {subMenuList}
      </Menu>
    </Sider>
  );
};

export default SideMenuComponent;

In WebHeaderComponent, I got a select input below. What I do is updating a value in context once I change my value from my select input, I prefer sideMenuComponent would be refreshed with new menulist which come from context.

However, error message is prompted.

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application.

Upvotes: 0

Views: 164

Answers (1)

Prateek Thapa
Prateek Thapa

Reputation: 4938

LIFTING STATE UP

  • Lift your state up to the common ancestor (i.e Parent).

  • Since both can access the state, you could pass the callback from one child to update the parent.

  • Which in turn updates another child since both children share the same state.

export default function Parent() {
  const [text, setText] = React.useState('not yet updated');

  return (
    <div>
      <Child1 text={text} />
      <Child2 onClick={setText} />
    </div>
  );
}

function Child1({ text }) {
  return <p>{text}</p>
}

function Child2({ onClick }) {
  return <button onClick={() => onClick('Child2 updated the state')}>Update</button>
}

Upvotes: 0

Related Questions