62

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

kid
  • 771
  • 1
  • 4
  • 12

8 Answers8

91

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')
)
Lin Du
  • 88,126
  • 95
  • 281
  • 483
25

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'))
});
Youssouf Oumar
  • 29,373
  • 11
  • 46
  • 65
Spencer Blum
  • 251
  • 2
  • 2
  • 14
    You don't need to make it a route just wrap it with BrowserRouter and the problem would be solved – Okechukwu Obi Mar 19 '22 at 15:47
  • 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: ) – grizzled_carcass Sep 25 '22 at 14:25
17

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
fresskoma
  • 25,481
  • 10
  • 85
  • 128
brense
  • 272
  • 3
  • 5
  • 1
    For me it was `` 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. – user3788955 Nov 03 '22 at 15:17
  • 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" – kluverp Jul 25 '23 at 09:33
12

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>
  );
}
Youssouf Oumar
  • 29,373
  • 11
  • 46
  • 65
Rahul Sharma
  • 9,534
  • 1
  • 15
  • 37
2

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);
Super Jade
  • 5,609
  • 7
  • 39
  • 61
1

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>
  
Tejas Veer
  • 413
  • 3
  • 6
1

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()
  })
})
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 like this:

  test('test app',()=>{
    render(<Mocktest/>);
    const yourTag= screen.findByLabelText(/choose time/i);
    expect(yourTag).toBeInTheDocument();
  });