Have you encountered The PUSH of UNDEFINED
Why this.props.history is undefined and why we cannot call method push of undefined?
Let us understand how this problem arises and what are the ways to solve it.
The Problem
We want to navigate to a different page, we are using react-router but we don’t find history object in props.
The component hierarchy usually looks like this.
<BrowserRouter>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/notHome" component={NotHome} />
<Route path="/anotherComponent" component=
{AnotherComponent} />
</Switch>
</BrowserRouter>
Let's say we have a Footer component inside NotHome and on the link click
we want to navigate to AnotherComponent.
NotHome.jsx<>
<h1>Not Home</>
<Footer />
</>Footer.jsxconst goToAnotherComponent = () => {
props.history.push("/anotherComponent")
}const goToHome = () => {
props.history.push("/")
}<>
<button role="link" onClick={goToAnotherComponent}>
Another Component
</button>
<button role="link" onClick={goToHome}>
Home
</button>
</>
When we click on the button we get the error props.history is undefined.
But we have wrapped it in Browser Router :(.
Let's find out what went wrong.
Why this happens
We get history object from the Route component.
When we pass a component to Route as <Route component={comp}/>
Route calls React.createElement(component, props) and this props contains the history object.
So Home, NotHome, and AnotherComponent have the history object in the props but the Footer component does not, as it is not associated with Route.
The Solution
First Solution:
Pass props to the Footer from each route component that uses Footer.
NotHome.jsxconst NotHome = props => {
<>
<h1>Not Home</>
<Footer props={props} />
</>
}
As NotHome will have history in props, Footer will get the same props and then we can access the history object.
But using this approach we will have to pass props from each of the parent components of Footer which is not very efficient.
Second Solution:
Wrap the Footer component with withRouter. This will provide the Footer component all the functionality that the Route provides and it will get the access to history object from props.
Footer.jsximport { withRouter } from 'react-router-dom'const Footer = (props) => {
const goToAnotherComponent = () => {
// props.history is defined now
props.history.push('/anotherComponent')
} return (
<>
// Footer content
</>
)}export default withRouter(Footer)
Third Solution
Use useHistory hook to get the history object.
Here we do not get access to history from props, rather we get it as the return value from the hook.
import { useHistory } from 'react-router-dom'const Footer = () => {
const history = useHistory() const goToAnotherComponent = () => {
// only history not props.history
history.push('/anotherComponent')
}return (
<>
// Footer content
</>
)}
The preferred way
I would not recommend the first way as it involves passing the props from above and we might not use the Footer component in a component that is part of Route.
The third solution is a cleaner way where we use useHistory hook and keep our props clean.
If we want the complete route functionality (props including history, match, location) we can wrap it with withRouter and access each of these from props.
References and Picture Credit
Image by Gerd Altmann from Pixabay