I ran head-first into this bug while wiring up a tiny demo. One moment everything compiled in react app, the next the browser console yelled at me:
Error
TypeError: _this.props.history is undefined
If you’re seeing the same thing, I’ve laid out exactly what I did wrong, why it explodes, and how I cleaned it up.
This is My index.js File
index.js
React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import { Router, Route, browserHistory } from 'react-router';
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/" component={App} />
</Router>,
document.getElementById('root')
);
App.js
React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
headerText: 'Props from Header.',
contentText: 'Props from content.'
};
}
render() {
return (
<div className="App">
<ul>
<li><a href="">Home</a></li>
<li><a href="">Home</a></li>
<li><a href="">Home</a></li>
</ul>
</div>
);
}
}
export default App;
Explain Error
What I saw | What really happens |
---|---|
history shows up as undefined inside the router | I mixed react-router v3 imports (Router , browserHistory ) with a newer react-router-dom v5/v6 runtime. The modern runtime never injects the history prop the way v3 did, so anything that reaches for this.props.history falls over. |
Quick Band aid
npm i react-router@3
That locks the project to the old API and the error disappears. I only recommend that when you cannot touch the rest of the stack (legacy codebase, tight deadline, etc.).
The Real Fix Move to react-router-dom@latest
Most projects will be happier on the current API. Below is my “mini-site” rewrite that:
- Uses v6 (freshest as of May 2025).
- Swaps full-page reloads for real SPA links.
- Adds extra pages, a code-driven redirect, and a 404 screen so you can practise.
Install
npm i react-router-dom
index.js
React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import App from './App';
import About from './About';
import Contact from './Contact';
import NotFound from './NotFound';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
App.js
(home page + menu + programmatic nav)
{ Link, useNavigate } from 'react-router-dom';
import './App.css';
function App() {
const go = useNavigate(); // lets me push a new URL from code
return (
<div className="App">
<h1>Home page</h1>
<nav>
<Link to="/">Home</Link>{' | '}
<Link to="/about">About</Link>{' | '}
<Link to="/contact">Contact</Link>
</nav>
<button onClick={() => go('/contact')}>
Go to Contact page
</button>
</div>
);
}
export default App;
About.js
default function About() {
return (
<section>
<h1>About</h1>
<p>This is a spare page so I (and you) can practise route changes.</p>
</section>
);
}
Contact.js
default function Contact() {
return (
<section>
<h1>Contact</h1>
<p>Imagine a form or address info here.</p>
</section>
);
}
NotFound.js
default function NotFound() {
return (
<section>
<h1>404</h1>
<p>Nothing is here. Use the menu to pick a valid page.</p>
</section>
);
}
What I Gained
- Correct wrapper –
<BrowserRouter>
automatically shareslocation
and friends with every child. No manual prop drilling. - Declarative links –
<Link>
tags flip the route instantly without refreshing the whole page. - Programmatic moves –
useNavigate()
lets me jump after a fetch, timer, or button click. - Nested routes – I can drop more
<Route>
elements inside any page to build layouts that swap only sub-regions. - 404 fallback – The
"*"
path makes sure strangers don’t land on a blank screen.
Give it a spin:
- Try adding
/settings
and create aSettings.js
component. - Replace the
<button>
logic with a post-login redirect. - Nest an “edit” view under
/contact/:id
and grabid
withuseParams()
.
Each change will work without the infamous history
error because the right router is now steering the ship.
Final Thought
Catching this bug reminded me how easy it is to a snippet from an old blog and forget that React’s ecosystem moves fast. Aligning the library version with the API you import is half the battle. The other half is taking a few minutes to add real links, programmatic navigation, and a 404 route so future me (and any teammate) can extend the project without tripping over the same mistake.