Cannot read property 'history' of undefined (useHistory hook of React Router 5)
Asked Answered
C

7

64

I am using the new useHistory hook of React Router, which came out a few weeks ago. My React-router version is 5.1.2. My React is at version 16.10.1. You can find my code at the bottom.

Yet when I import the new useHistory from react-router, I get this error:

Uncaught TypeError: Cannot read property 'history' of undefined

which is caused by this line in React-router

function useHistory() {
  if (process.env.NODE_ENV !== "production") {
    !(typeof useContext === "function") ? process.env.NODE_ENV !== "production" ? invariant(false, "You must use React >= 16.8 in order to use useHistory()") : invariant(false) : void 0;
  }

  return useContext(context).history; <---------------- ERROR IS ON THIS LINE !!!!!!!!!!!!!!!!!
}

Since it is related to useContext and perhaps a conflict with context is at fault, I tried completely removing all calls to useContext, creating the provider, etc. However, that did nothing. Tried with React v16.8; same thing. I have no idea what could be causing this, as every other feature of React router works fine.

***Note that the same thing happens when calling the other React router hooks, such as useLocation or useParams.

Has anyone else encountered this? Any ideas to what may cause this? Any help would be greatly appreciated, as I found nothing on the web related to this issue.

import React, {useEffect, useContext} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { Switch, useHistory } from 'react-router'
import { useTranslation } from 'react-i18next';

import lazyLoader from 'CommonApp/components/misc/lazyLoader';
import {AppContext} from 'CommonApp/context/context';

export default function App(props) {
    const { i18n } = useTranslation();
    const { language } = useContext(AppContext);
    let history = useHistory();

    useEffect(() => {
        i18n.changeLanguage(language);
    }, []);

    return(
        <Router>
            <Route path="/">
                <div className={testClass}>HEADER</div>
            </Route>
        </Router>
    )
}

Cinchonism answered 3/10, 2019 at 14:12 Comment(0)
H
112

It's because the react-router context isn't set in that component. Since its the <Router> component that sets the context you could use useHistory in a sub-component, but not in that one.

Here is a very basic strategy for solving this issue:

const AppWrapper = () => {
  return (
    <Router> // Set context
      <App /> // Now App has access to context
    </Router>
  )
}

const App = () => {
  let history = useHistory(); // Works!
...
// Render routes in this component

Then just be sure to use the "wrapper" component instead of App directly.

Hypnotism answered 3/10, 2019 at 15:1 Comment(4)
Thank you Brian. That was indeed the cause of the issue. :)Cinchonism
they should use just useState useContext just complicated thingWilderness
...And the solution might be to lift the Router up.Prescriptible
Where to use the AppWrapper ?Vaas
S
40

Note to other people that run into this problem and already have wrapped the component with Router component. Make sure that Router and the useHistory hook are imported from the same package. The same error can be thrown when one of them are imported from react-router and the other one from react-router-dom and the package versions of those packages don't match. Don't use both of them, read about the difference here.

Shoeshine answered 17/11, 2019 at 16:19 Comment(2)
zomg... thank you, thank you so much. If I could upvote this answer more than once, I would!Importance
very precise and wise :)Doughty
F
10

useHistory won't work in the component where you have your Routes because the context which is needed for useHistory is not yet set.

useHistory will work on any child component or components which you have declared in your Router but it won't work on Router's parent component or Router component itself.

Forlini answered 17/6, 2020 at 13:12 Comment(0)
H
5

The solution is:

in the Main (father) component

import { BrowserRouter } from "react-router-dom";

<BrowserRouter><App /></BrowserRouter>

in the child component (App)

import { withRouter } from "react-router-dom";


function App(props) {
    const { i18n } = useTranslation();
    const { language } = useContext(AppContext);
    let history = useHistory();

    useEffect(() => {
        i18n.changeLanguage(language);
    }, []);

    return(

            <Route path="/">
                <div className={testClass}>HEADER</div>
            </Route>

    )
}

export default withRouter(App);
Heflin answered 11/6, 2020 at 15:50 Comment(1)
withRouter() doesn't seem to be needed. The example can be significantly reduced and improved (e.g. onClick={history.replace(...)}).Prescriptible
C
2

I updated my react-router-dom from 5.0.0 to ^5.1.2 and it's been solved. You may notice that the useHistory is in a sub-component.

Cloudland answered 28/2, 2020 at 10:4 Comment(0)
W
0

Use BrowserRouter.

import {
  BrowserRouter as Router,
  Route,
  Switch,
} from 'react-router-dom';

If you use Router, then you need to specify a history for it:

import {
  Router,
  Route,
  Switch,
} from 'react-router-dom';

// Ensure you destructure the createBrowserHistory object
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();
return (
  <Router history={history} >
    ...
  </Router>
);
Wingfooted answered 20/10, 2020 at 16:55 Comment(0)
T
0

In a short, you should move const history = useHistory(); to your sub-component

Tc answered 27/10, 2021 at 10:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.