useNavigate() may be used only in the context of a <Router> component
Asked Answered
U

8

86

See my code below. I'm trying to add this button that goes back to the previous page using react-router-dom but I get the below error, and also all the components on my website disappear.

Error:

useNavigate() may be used only in the context of a component

My code:

function App() {
  const navigate = useNavigate();
  return (
    <BrowserRouter>
      <div>
      <button onClick={() => navigate(-1)}>go back</button>
        <Nav/>
        <Routes>
          <Route exact path="/" element={<Home/>}/>
          <Route exact path="/home" element={<Home/>}/>
          <Route exact path="/upcoming/:user" element={<Upcoming/>}/>
          <Route exact path="/record/:user" element={<Record/>}/>
          <Route path="*" element={<NotFound/>}/>
        </Routes>
      </div>
    </BrowserRouter>
  );
}

And this is the error I got in console:

Error

Unfix answered 27/12, 2021 at 4:50 Comment(0)
S
115

This error throws in useNavigate. useInRouterContext will check if the component(which uses useNavigate hook) is a descendant of a <Router>. If the component is not a descendant of the <Router> component, it will throw this error.

Here's the source code to explain why you can't use useNavigate, useLocation outside of the Router component:

useNavigate uses useLocation underly, useLocation will get the location from LocationContext provider. If you want to get the react context, you should render the component as the descendant of a context provider. Router component use the LocationContext.Provider and NavigationContext.Provider. That's why you need to render the component as the children, so that useNavigate hook can get the context data from NavigationContext and LocationContext providers.

Your environment is browser, so you need to use BrowserRouter. BrowserRouter is built based on Router.

Refactor to this:

App.jsx:

function App() {
  const navigate = useNavigate();
  return (
      <div>
        <button onClick={() => navigate(-1)}>go back</button>
        <Nav/>
        <Routes>
          <Route exact path="/" element={<Home/>}/>
          <Route exact path="/home" element={<Home/>}/>
          <Route exact path="/upcoming/:user" element={<Upcoming/>}/>
          <Route exact path="/record/:user" element={<Record/>}/>
          <Route path="*" element={<NotFound/>}/>
        </Routes>
      </div>
  );
}

index.jsx:

import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

ReactDOM.render(
  <BrowserRouter>
    <App/>
  </BrowserRouter>,
  document.getElementById('root')
)
Spiritualty answered 27/12, 2021 at 5:5 Comment(0)
C
30

I had this same issue but with testing a component that has the hook useNavigate on it. Solved it by wrapping my component in a Route with Routes and BrowserRouter. Hope this helps others with the same error.

let container = null;
    beforeEach(() => {
    container = document.createElement("div");
    document.body.appendChild(container);
});

afterEach(() => {
    // cleanup on exiting
    unmountComponentAtNode(container);
    container.remove();
    container = null;
});

test('finds qualified insurrance policies', () => {
    act(() => {
    render(
    <BrowserRouter>
        <Routes>   
            <Route path="*" element= {<QuaifiedPolicies policyTypes={false} />}/>
        </Routes>
    </BrowserRouter>
        , container);
    });
    expect(screen.getByLabelText('some text'))
});
Carpic answered 14/2, 2022 at 4:6 Comment(2)
You don't need to make it a route just wrap it with BrowserRouter and the problem would be solvedMoreta
Adding another level to this, if you have BrowserRouter but also are using Redux you need to have a Provider and store as well, otherwise you will get the 'can not find getState...' error. Just letting anyone else interested know: )Scandent
Y
28

For future reference: This error may also occur

  • if you import your <Router> from react-router-dom and import useNavigate from react-router
  • if you import useNavigate et al from a different version of the package than BrowserRouter, e.g. v5 and v6
Yokum answered 14/9, 2022 at 8:44 Comment(5)
For me it was <BrowserRouter> was imported from v5 and useNavigate from v6 (big project, slowly migrating over to v6 little by little). But this answer gave me the hint to check my import.Hannie
This is a tricky one, but this answer really helped me with above error. I was using "react-router-dom", but VSCode sneaky imported from "react-router-dom6"Valdes
This was my issue on react-router-dom version 6.10 vs 6.16. Thank you!Disciplinarian
heavy thumbs up for this gem. difficult and sneaky problem.Interlace
Thanks for this extra insight! I was sitting there looking at the Component hierarchy for 15 mins or so... "but it IS inside the Router!"Rush
S
18

You are using hook outside BrowserRouter provider. That's why you are getting errors. Refactor your component like below to solve this issue:

function Root() {
  const navigate = useNavigate();
  return (
    <div>
      <button onClick={() => navigate(-1)}>go back</button>
      <Nav />
      <Routes>
        <Route exact path="/" element={<Home />} />
        <Route exact path="/home" element={<Home />} />
        <Route exact path="/upcoming/:user" element={<Upcoming />} />
        <Route exact path="/record/:user" element={<Record />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Root />
    </BrowserRouter>
  );
}
Sadyesaechao answered 27/12, 2021 at 5:5 Comment(0)
C
6

Problem

My exact error was

console.error
      Error: Uncaught [Error: useNavigate() may be used only in the context of a <Router> component.]

and it happened only when running the following unit test code:

// Act
const button: HTMLElement = render(<MyButton myChoice="myChoice" />).container;

// Assert
expect(button).toHaveTextContent("myChoice");

Solution

I solved the error by wrapping the rendered element with <BrowserRouter>:

// Act
const button: HTMLElement = render(
    <BrowserRouter>
        <MPBenefitChoiceButton selectedChoice={selectedChoice} />
    </BrowserRouter>
).container;

// Assert
expect(button).toHaveTextContent(buttonText);
Crude answered 8/11, 2022 at 1:7 Comment(1)
Worked for my code-base , thanks!Garrott
H
2

The component where you use useNavigate should wrap by <Router> ... </Router>

e.g.

<Router>
      <AuthProvider>    <----- useNavigate used in this component 
        <NavBar />
        <Routes>
          <Route exact path="/login" element={<LoginForm />} />
          <Route exact path="/register" element={<RegisterForm />} />
        </Routes>
      </AuthProvider>
</Router>
<OtherComponent/>  <----- if useNavigate use inside this component then  
                         error occur because useNavaigate work inside  
                         <Router> Component so it should wrap inside <Router>

Like this :

<Router>
      <AuthProvider>    
        <NavBar />
        <Routes>
          <Route exact path="/login" element={<LoginForm />} />
          <Route exact path="/register" element={<RegisterForm />} />
        </Routes>
      </AuthProvider>
      <OtherComponent/>
</Router>
  
Hereunto answered 5/12, 2022 at 11:40 Comment(0)
D
2

In the unit tests wrap your components with MemoryRouter and provide the initialEntries correctly with pathname and search params

import { MemoryRouter, Routes, Route } from 'react-router-dom'
import { renderWithProviders, screen } from '../../../test/test-utils'

import { Case } from '../index'
import { Home } from '../../home/index'

describe('Case component', () => {
  const queryParams = 'caseRef=2023030026&token=testToken&refreshToken=testRefreshToken'

  it('renders "Loading..." when token, refreshToken, or caseRef are missing', () => {
    renderWithProviders(
      <MemoryRouter initialEntries={[{ pathname: '/case?', search: queryParams }]}>
        <Case />
      </MemoryRouter>
    )
    expect(screen.getByText(/Loading.../)).toBeInTheDocument()
  })

  it('renders Home once the application has loaded', () => {
    renderWithProviders(
      <MemoryRouter initialEntries={['/case', '/2023030026']}>
        <Routes>
          <Route path="/:caseRef" element={<Home />} />
        </Routes>
      </MemoryRouter>
    )
    expect(screen.getByText(/Overview/)).toBeInTheDocument()
  })
})
Dunseath answered 5/7, 2023 at 17:39 Comment(0)
A
1

You can also make a mock tag and wrap the tag you want to test inside a

<BroserRouter></BroserRouter>

tag like this:

const Mocktest =()=>{
  return(
    <BrowserRouter>
      <App/>
    </BrowserRouter>
  )
}

and then render the Mocktest component like this:

  test('test app',()=>{
    render(<Mocktest/>);
    const yourTag= screen.findByLabelText(/choose time/i);
    expect(yourTag).toBeInTheDocument();
  });
Attendant answered 16/7, 2023 at 10:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.