react-router throwing TypeError: useContext(...) is undefined when using useHistory

Viewed 9018

On my own code I tried to use react-router's useHistory by adding it to the imports:

import {BrowserRouter as Router, Link, Route, Switch, useHistory} from "react-router-dom";

and then defining a variable with it on my App() function:

let history = useHistory();

When I do that, I get the error:

TypeError: useContext(...) is undefined

coming from react-router's hooks.js, the specific line is:

return useContext(Context).history;

The whole file looks like this:

import React from "react";
import invariant from "tiny-invariant";

import Context from "./RouterContext.js";
import matchPath from "./matchPath.js";

const useContext = React.useContext;

export function useHistory() {
  if (__DEV__) {
    invariant(
      typeof useContext === "function",
      "You must use React >= 16.8 in order to use useHistory()"
    );
  }

  return useContext(Context).history;
}

export function useLocation() {
  if (__DEV__) {
    invariant(
      typeof useContext === "function",
      "You must use React >= 16.8 in order to use useLocation()"
    );
  }

  return useContext(Context).location;
}

export function useParams() {
  if (__DEV__) {
    invariant(
      typeof useContext === "function",
      "You must use React >= 16.8 in order to use useParams()"
    );
  }

  const match = useContext(Context).match;
  return match ? match.params : {};
}

export function useRouteMatch(path) {
  if (__DEV__) {
    invariant(
      typeof useContext === "function",
      "You must use React >= 16.8 in order to use useRouteMatch()"
    );
  }

  return path
    ? matchPath(useLocation().pathname, path)
    : useContext(Context).match;
}

Some more context:

enter image description here

I tried accessing React.useContext on my own code and it is defined and it is a function.

Any ideas what might be going on here?

5 Answers

I think that you should wrap your App in index.js with the BrowserRouter (as Router) and then in your App you define the Switch and Routes. Because you cannot use useHistory or useLocation in the same file where you use BrowserRouter. So, use BrowserRouter wrapper one level up.

That happens because you need to wrap your component in a Router element. e.g:

import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
// import MyComponent

const history = createBrowserHistory();

const MyApp = ()=> (
  <Router {...{ history }}>
    <MyComponent />
  </Router>
);

export default MyApp;

It's definately not the case for this specific issue, but maybe someone will end up with a similar issue as I did.

For me the solution was to import hooks from react-router-dom instead of react-router package.

In my case I was trying to implement custom hook in another local package and it turned out that package didn't have react-router nor react-router-dom as dependency. Error message was exactly the same and no other error from IDE or compiler. Took me quite a while to figure this out. So the bottom line is: double-check your dependencies.

I've run into a similar issue with useRouteMatch(). I'm not sure if the cause is the same. I receive the error Cannot read property 'match' of undefined from line useContext(Context).match; when calling useRouteMatch() in my tests.


Option 1:

One of the ways the return from useContext can be undefined is if the Context supplied to useContext doesn't include any data. For example if you remove value={{ name: "Pupeno" }} from https://codesandbox.io/s/react-hooks-usecontext-example-wv76d?file=/src/index.js:320-347 you'll see a similar error.

There could be a similar bug in react-router-dom that allows the Context to be empty when it's called from these hooks.


Option 2:

It's hard to tell without looking at your code. It could also be something like https://github.com/ReactTraining/react-router/issues/7332

Related