useLocation and useHistory undefined in React Router

By

Why do useLocation and useHistory return undefined in React Router? Because the Router wraps the same component using them, so move the Router one level up.

~~~

If useLocation and useHistory return undefined, you are most likely calling them in the same component that renders the <Router>. The hooks only work in components rendered inside the Router. Move the Router one level up and they work.

Here’s how I found this out.

I was having some head scratching moment when using the useLocation and useHistory hooks with React Router.

const history = useHistory()
const location = useLocation()

They both returned undefined.

Turns out I was adding the Router to the DOM with <Router>...</Router> in the same component I was using useLocation and useHistory. Something like this:

import { BrowserRouter as Router, useHistory, useLocation } from 'react-router-dom'

const App = () => {
  const history = useHistory() // undefined
  const location = useLocation() // undefined

  return (
    <Router>
      {/* routes */}
    </Router>
  )
}

Then I found this issue that explained I could not do that.

Why does this happen?

React Router shares the history and location objects with your components through React context. A context provider only serves the components rendered inside it, its descendants in the tree.

App is not a descendant of the Router. It’s the component that renders it. The hooks in App run before the <Router> even appears in the tree, so there’s no context value for them to read.

That’s why you don’t get an error, just undefined. The hooks work, but they find nothing.

The fix

I had to move the <Router>...</Router> wrapping of my component one level up. In my case, I did that in the index.js file:

import { BrowserRouter as Router } from 'react-router-dom'

...

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
  document.getElementById('root')
)

Now App is rendered inside the Router, so useHistory and useLocation work in App and in any component below it.

If you’d rather not touch index.js, there’s another way. Keep the Router in App, but move the code that needs the hooks into a child component rendered inside <Router>. Both approaches solve the same problem: the hooks must be called by a component that lives under the Router in the tree.

Tagged: React · All topics
~~~

Related posts about react: