debugging: react-router's location undefined

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:

App.tsx
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.

Wrapper.tsx
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:

EntryPoint.tsx
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!