Search code examples
reactjsreact-hooksreact-router-dommobx

How to solve conflict between react-router-dom v6 and mobx?


I've created dynamic routing on my site, which changes when a user login successfully. The fact of logging I keep in global state, which observers by mobx. When the user login successfully, routes changes too, and it works correctly, but in the console, there is the next problem: Error

Error in text variant:

react-dom.development.js:67 Warning: React has detected a change in the order of Hooks called by AppRouter. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks

Previous render Next render

  1. useState useState
  2. useState useState
  3. useRef useRef
  4. useDebugValue useDebugValue
  5. useEffect useEffect
  6. useContext useContext
  7. undefined useContext ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

There is a screenshot of the route's component: Routes component Routes component code:

import { observer } from "mobx-react-lite";
import { useContext } from "react";
import { Navigate, Route, Routes } from "react-router-dom";
import { Context } from "../..";
import { adminPaths, guestPaths, userPaths } from "./paths";
const AppRouter = () => {
  const { userStore } = useContext(Context);
  return (
    <Routes>
      <Route path='/*' element={<Navigate to='/' />} />
      {
        userStore.isAuth && userPaths.map(({ path, component }) => 
          <Route path={path} element={component()} />)
      }
      {
        userStore.isAuth && adminPaths.map(({ path, component }) => 
          <Route path={path} element={component()} />)
      }
      {
        guestPaths.map(({ path, component }) => <Route path={path} element={component()} />)
      }
    </Routes>
 )
}
export default observer(AppRouter);

When I remove the observer in this component, error disappeared, but routes don't update after login.

Routes configuration code: Routes configuration Routes configuration code:

import AdminCabinet from "../../pages/admin-cabinet/admin-cabinet";
import HomePage from "../../pages/home-page/home-page";
import UserCabinet from "../../pages/user-cabinet/user-cabinet";

export const guestPaths = [
    {
        name: 'Home',
        path: '/',
        component: HomePage
    }
];
export const userPaths = [
    {
        name: 'Personal cabinet',
        path: '/personalCabinet',
        component: UserCabinet
    }
];
export const adminPaths = [
    {
        name: 'Admin cabinet',
        path: '/adminCabinet',
        component: AdminCabinet
    }
];

I would be grateful if someone helps me with this problem.


Solution

  • Issue

    The only overt issue I see with your code is that you are directly invoking your React components instead of rendering them as JSX for React to handle and manage the component lifecycle of.

    Example:

    import UserCabinet from "../../pages/user-cabinet/user-cabinet";
    
    const userPaths = [
      {
        name: "Personal cabinet",
        path: "/personalCabinet",
        component: UserCabinet,
      },
    ];
    

    ...

    const AppRouter = () => {
      const { userStore } = useContext(Context);
    
      return (
        <Routes>
          ...
          {userStore.isAuth && userPaths.map(({ path, component }) => (
            <Route path={path} element={component()} /> // <-- invoking component
          ))}
          ...
        </Routes>
      );
    };
    

    Solution

    The element prop should receive JSX. When destructuring component rename it to Component so it has a valid React component name and render as JSX. Don't forget to use a valid React key for the mapped routes.

    Example:

    import UserCabinet from "../../pages/user-cabinet/user-cabinet";
    
    const userPaths = [
      {
        name: "Personal cabinet",
        path: "/personalCabinet",
        component: UserCabinet,
      },
    ];
    

    ...

    const AppRouter = () => {
      const { userStore } = useContext(Context);
    
      return (
        <Routes>
          ...
          {userStore.isAuth && userPaths.map(({ path, component: Component }) => (
            <Route
              key={path}
              path={path}
              element={<Component />} // <-- pass as JSX
            />
          ))}
          ...
        </Routes>
      );
    };