kid
kid

Reputation: 1061

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

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

Upvotes: 91

Views: 244488

Answers (8)

Masuod Fathi
Masuod Fathi

Reputation: 39

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();
  });

Upvotes: 1

Archit Sharma
Archit Sharma

Reputation: 31

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()
  })
})

Upvotes: 2

brense
brense

Reputation: 412

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

Upvotes: 31

Lin Du
Lin Du

Reputation: 102207

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')
)

Upvotes: 118

Tejas Veer
Tejas Veer

Reputation: 533

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>
  

Upvotes: 2

Super Jade
Super Jade

Reputation: 6345

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);

Upvotes: 6

Rahul Sharma
Rahul Sharma

Reputation: 10071

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>
  );
}

Upvotes: 18

Spencer Blum
Spencer Blum

Reputation: 311

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'))
});

Upvotes: 31

Related Questions