Search code examples
reactjsreact-routerreact-router-dom

redirect routes with react state


I would like to check if my cms is installed (with a api call to my backend). I can fetch the value, I can see the "ok" value into console, but I'm not redirected to /login as expected. I suspect const content = useRoutes(routes(isInstalled, isLogged)); is not updated, isInstalled state, when is updated, is not send with its new value to routes.js.

App.js:

function App() {
  const [isLogged, setIsLogged] = useState<boolean>(false);
  const [isInstalled, setIsInstalled] = useState<boolean>(false);
  const [client, setClient] = useState<any>();
  const content = useRoutes(routes(isInstalled, isLogged));
  const location = useLocation();

  useEffect(() => {
    const transport = createGrpcWebTransport({
      baseUrl: `http://127.0.0.1:50051`,
    });
    const client = createPromiseClient(Lucle, transport);
    setClient(client);

    if (location.pathname == "/admin") {
      check_if_installed(client)
        .then(() => {
         console.log("ok");
         setIsInstalled(true)
         })
        .catch(() => {
          console.log("NOK");
          setIsInstalled(false)
        });
    }
  }, []);

  return (
    <StyledEngineProvider injectFirst>
      <ThemeProvider theme={theme}>
        <GlobalStyles />
        {content}
      </ThemeProvider>
    </StyledEngineProvider>
  );
}

routes.js

const routes = (isInstalled: boolean, isLogged: boolean) => [
  { path: "/login", element: <Login /> },
  {
    path: "admin",
    element: !isInstalled ? (
      <Navigate to="/install" />
    ) : !isLogged ? (
      <Navigate to="/login" />
    ) : (
      <Dashboard />
    ),
    children: [
      { path: "", element: <AdminIndex /> },
      { path: "editor", element: <OnlineEditor /> },
      { path: "tables", element: <Tables /> },
    ],
  },
  {
    path: "/install",
    element: <Install />,
  },
];

How to update routes ?


Solution

  • Navigation to either of "/login" or "/install" only occurs when on the "/admin" route, and since both isLogged and isInstalled start out initially false, the UI is going to be navigated to either of "/login" or "/install" if starting from "/admin". Neither "/login" or "/install" check the state and navigate anywhere else.

    You basically need to implement a route protection scheme.

    Here's a basic example of route protection using your state and routes.

    import { Navigate, Outlet } from "react-router-dom";
    
    const AnonymousRoutes = ({ isLogged }: { isLogged: boolean }) => {
      return isLogged ? <Navigate to="/admin" replace /> : <Outlet />;
    };
    
    const PrivateRoutes = ({ isLogged }: { isLogged: boolean }) => {
      return isLogged ? <Outlet /> : <Navigate to="/login" replace />;
    };
    
    const InstalledRoutes = ({ isInstalled }: { isInstalled: boolean }) => {
      return isInstalled ? <Outlet /> : <Navigate to="/install" replace />;
    };
    
    const UninstalledRoutes = ({ isInstalled }: { isInstalled: boolean }) => {
      return isInstalled ? <Navigate to="/admin" replace /> : <Outlet />;
    };
    
    const routes = (isInstalled: boolean, isLogged: boolean) => [
      {
        element: <AnonymousRoutes isLogged={isLogged} />,
        children: [{ path: "/login", element: <Login /> }]
      },
      {
        element: <PrivateRoutes isLogged={isLogged} />,
        children: [
          {
            element: <InstalledRoutes isInstalled={isInstalled} />,
            children: [
              {
                path: "admin",
                element: <Dashboard />,
                children: [
                  { index: true, element: <AdminIndex /> },
                  { path: "editor", element: <OnlineEditor /> },
                  { path: "tables", element: <Tables /> }
                ]
              }
            ]
          },
          {
            element: <UninstalledRoutes isInstalled={isInstalled} />,
            children: [{ path: "/install", element: <Install /> }]
          }
        ]
      }
    ];
    

    Now if while any of these routes are currently rendered and the "app state" changes, the access to that route will be re-evaluated and conditionally allow the Outlet to be rendered and the "protected" content to be rendered or a redirect to a "safe route" is rendered. For example, if a user is unauthenticated and accesses a "ProtectedRoutes" route, they redirect to "/login", and vice-versa, if they are already authenticated and attempt to access "/login" the UI will bounce them off that route back to "/admin".

    Demo

    Edit redirect-routes-with-react-state