Search code examples
javascriptreactjsreact-routerreact-router-dom

Components are not rendered but the URL changes after updating to React-Router-DOM v6


I have updated to react-router-dom version 6 and the URL I see that it's updated but then the components are not rendered. Also when I refresh the page the component is still not rendering.

I have this code:

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import { createBrowserHistory } from 'history'

const history = createBrowserHistory()

function App() {
  return (
    <Router history={history}>
      <Routes>
        <Route path='/logout' element={<Logout/>} />
        <Route path='/' element={<Authenticate/>}>
          <Route index element={<Analysis />} />
          <Route path='/analysis' element={<Analysis />} />
          <Route path='/login' element={<Login/>} />
          <Route path='/register' element={<Register/>} />
          .....
        </Route>
      </Routes>
    </Router>
  )
}

const rootNode = document.getElementById('root')
ReactDOM.render(<App />, rootNode)

The only component that it does render is the <Authenticate/> component. Inside this component when I am changing routes I have this code implemented

handleClick = e => {
  console.log('e is :', e.key)
  if (e.key !== '/logout') {
    history.push(e.key)
  }
}

Do I have something wrong on my implementation or something else?

I am using:

"react-router-dom": "^6.22.1",
"react": "^17.0.2",
"history": "^5.3.0",

Solution

  • Issue(s)

    The BrowserRouter component doesn't consume any history prop

    declare function BrowserRouter(
      props: BrowserRouterProps
    ): React.ReactElement;
    
    interface BrowserRouterProps {
      basename?: string;
      children?: React.ReactNode;
      future?: FutureConfig;
      window?: Window;
    }
    

    so the likely issue here is that your custom history reference is effecting navigation actions outside the BrowserRouter component's scope. In other words, the router is completely unaware of them and doesn't react to the external URL changes.

    Because Authenticate is a layout route component, it must render an Outlet component for the nested routes to render out their element content when the URL path is matched. Ensure this is the case.

    Solution

    Using BrowserRouter (Preferred)

    Use the useNavigate hook and navigate function to effect navigation actions. Update Authenticate and any other routed component to use the useNavigate hook.

    Example:

    import { Outlet, useNavigate } from 'react-router-dom';
    
    const Authenticate = () => {
      const navigate = useNavigate();
    
      handleClick = e => {
        if (e.key !== '/logout') {
          navigate(e.key); // <-- issue PUSH action
        }
      };
    
      ...
    
      return (
        ...
        <Outlet /> // <-- nested routes render content here
        ...
      );
    };
    

    Using HistoryRouter and custom history object

    Import and use the undocumented HistoryRouter. Create and export your history object separately so all components reference the same history reference.

    Example:

    history.js

    import { createBrowserHistory } from 'history';
    const history = createBrowserHistory();
    export default history;
    
    import {
      unstable_HistoryRouter as Router,
      Routes,
      Route
    } from 'react-router-dom';
    import history from './path/to/history';
    
    function App() {
      return (
        <Router history={history}>
          <Routes>
            <Route path='/logout' element={<Logout/>} />
            <Route path='/' element={<Authenticate/>}>
              <Route index element={<Analysis />} />
              <Route path='/analysis' element={<Analysis />} />
              <Route path='/login' element={<Login/>} />
              <Route path='/register' element={<Register/>} />
              .....
            </Route>
          </Routes>
        </Router>
      )
    }
    

    Authenticate.jsx

    import { Outlet } from 'react-router-dom';
    import history from './path/to/history';
    
    const Authenticate = () => {
      ...
    
      handleClick = e => {
        if (e.key !== '/logout') {
          history.push(e.key); // <-- issue PUSH action
        }
      };
    
      ...
    
      return (
        ...
        <Outlet /> // <-- nested routes render content here
        ...
      );
    };
    

    Only use this version if you really have a need for the external custom history object, e.g. you are connecting your router to a Redux store or similar, otherwise, the first option is preferred.