2021-04-12
|~2 min read
|375 words
While working on getting a create-react-app bootstrapped application to work within single-spa, I ran into an apparently quite common error with react-router
:
react-router.min.js:1 Uncaught TypeError: Cannot read property 'location' of undefined
The issue, it would turn out, is that I was trying to use the hook, useLocation
too early. Per Tim Door.
You can’t use any of the hooks from within the same component that puts the Router into the tree.
You need to move your BrowserRouter out of that component. It can go in the ReactDOM.render() call, for instance.
What does that look like? In my case, it was something like this:
import React from "react"
import { Route, Switch, useLocation } from "react-router-dom"
import { Finally } from "./components/Finally"
import { Wrapper } from "./components/Wrapper"
export const App = () => {
const location = useLocation()
return (
<Wrapper>
<Switch location={location}>
<Route path="/finally">
<Finally />
</Route>
{/*...*/}
</Switch>
</Wrapper>
)
}
export default App
Notably, however, <Wrapper>
hid the problem. This is because <Wrapper>
was originally designed to go around the entry point of the application, and with the refactor (to accommodate single-spa
), I’d moved it here. The <Wrapper>
happens to be where we were mounting the Router into the tree.
import React from "react"
import { BrowserRouter as Router } from "react-router-dom"
import { AppContext, initialState, appReducer } from "../../data"
export const Wrapper = ({ children }: React.PropsWithChildren<{}>) => {
const [state, dispatch] = React.useReducer(appReducer, initialState)
return (
<AppContext.Provider value={{ state, dispatch }}>
<Router>{children}</Router>
</AppContext.Provider>
)
}
export default Wrapper
By interrogating the <Wrapper>
component and seeing the <Router>
component, Tim’s comment started to make sense. From there, it was solved easily by adding one extra layer of abstraction. A new EntryPoint
component works:
import * as React from "react"
import Wrapper from "./components/Wrapper"
import App from "./App"
export const EntryPoint = () => {
return (
<React.StrictMode>
<Wrapper>
<App />
</Wrapper>
</React.StrictMode>
)
}
export default EntryPoint
The only last step (not shown) is to remove the <Wrapper>
from <App>
.
With this small modification, the application worked exactly as expected! Onto the next problem.
Some additional resources if you’re interested:
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!