Home > Blockchain >  Can react-router v6 routes be declared in a functional component?
Can react-router v6 routes be declared in a functional component?

Time:12-29

I have an app which is only accessible past the login, I tried implementing it this way:

import React, {useState} from 'react';
import {Route, Routes} from 'react-router-dom';

import type Admin from '../models/admin';
import {AdminProvider} from './AdminContext';

import LoginC from './components/LoginC';
import RegisterC from './components/RegisterC';
import HomeC from './components/HomeC';

export const Server = () => {
    const [admin, setAdmin] = useState<Admin | undefined>();

    if (!admin) {
        return (
            <Routes>
                <Route path='/' element={<LoginC setAdmin={setAdmin} />} />
                <Route path='/register' element={<RegisterC setAdmin={setAdmin} />} />
            </Routes>
        );
    }

    return (
        <AdminProvider value={admin}>
            <div>Hello server my old friend!</div>
            <HomeC />
        </AdminProvider>
    );
};

export default Server;

The LoginC.tsx file has a link to the register page:

<Link to='/register'>Register instead</Link>

My intent was to return different routes when the user is logged in and when they're not.

But I get a 404 (React Router) error when I click on the "Register instead" link.

It's correctly matching the "/" route though and displaying the LoginC component.

Do all the routes need to be declared statically?

My intent was to return different routes depending on whether the user was set or not.

EDIT:

Here is the code creating the router:

import React from 'react';
import {createRoot} from 'react-dom/client';

import {ThemeProvider} from '@mui/material';

import {
    createBrowserRouter as createRouter,
    RouterProvider,
} from 'react-router-dom';

import theme from './theme';
import Server from './Server';

const router = createRouter([
    {
        path: '/',
        element: <Server />,
    },
]);

const elt = document.getElementById('app');

if (!elt) {
    throw new Error('No element with id "app" found');
}

createRoot(elt).render((
    <React.StrictMode>
        <ThemeProvider theme={theme}>
            <RouterProvider router={router} />
        </ThemeProvider>
    </React.StrictMode>
));

CodePudding user response:

Alright, I needed to not create the routes in createBrowserRouter and use BrowserRouter directly as such:

import React from 'react';
import {createRoot} from 'react-dom/client';

import {ThemeProvider} from '@mui/material';

import {BrowserRouter} from 'react-router-dom';

import theme from './theme';
import Server from './Server';

const elt = document.getElementById('app');

if (!elt) {
    throw new Error('No element with id "app" found');
}

createRoot(elt).render(
    <React.StrictMode>
        <ThemeProvider theme={theme}>
            <BrowserRouter>
                <Server />
            </BrowserRouter>
        </ThemeProvider>
    </React.StrictMode>
);

CodePudding user response:

The issue is that the Server is rendered on "/" and attempting to render descendent routes. In order for descendent routes to be matched and rendered the parent route necessarily needs to append the "*" wildcard matcher to its path. The LoginC component rendered because the path was will "/", but "/register" was unable to be matched and render RegisterC.

const router = createRouter([
  {
    path: '/*', // <-- wildcard allows descendent route matching
    element: <Server />,
  },
]);

A more common route protection pattern is to use layout routes and components that check a protection condition and render an Outlet component or redirect to a non-protected route.

Example:

import { Navigate, Outlet } from 'react-router-dom';

const AdminLayout = ({ admin }) => {
  return admin
    ? (
        <AdminProvider value={admin}>
          <Outlet />
        </AdminProvider>
      )
    : <Navigate to="/login" replace />;
};
export const Server = () => {
  const [admin, setAdmin] = useState<Admin | undefined>();

  return (
    <Routes>
      <Route element={<AdminLayout admin={admin} />}>
        <Route path="/" element={<HomeC />} />
      </Route>
      <Route path='/login' element={<LoginC setAdmin={setAdmin} />} />
      <Route path='/register' element={<RegisterC setAdmin={setAdmin} />} />
    </Routes>
  );
};

And since it doesn't appear any of the RRDv6.4 Data APIs are being used, just render Server into a router.

import { BrowserRouter } from 'react-router-dom';

createRoot(elt).render((
  <React.StrictMode>
    <ThemeProvider theme={theme}>
      <BrowserRouter>
        <Server />
      </BrowserRouter>
    </ThemeProvider>
  </React.StrictMode>
));
  • Related