"Cannot read properties of undefined (reading 'pathname')" when testing pages in the v6 React Router
Asked Answered
K

3

13

When testing components with <Link>s, for example in my answer to Recommended approach for route-based tests within routes of react-router, I often use the following pattern to get access to the current location for testing purposes:

const renderInRouter = () => {
  const history = createMemoryHistory();
  const wrapper = render(
    <Router history={history}>
      <MyPage />
    </Router>
  );
  return { ...wrapper, history };
}

This worked fine up to v5.3, but after upgrading to v6 I get:

 FAIL  ./demo.test.js
  ✕ works (205 ms)

  ● works

    TypeError: Cannot read properties of undefined (reading 'pathname')

      at Router (../packages/react-router/index.tsx:281:5)

      ...

This use-case isn't covered in the migration docs, v6 has no testing guides so far and, although the API reference does show that the history prop is no longer expected:

interface RouterProps {
  basename?: string;
  children?: React.ReactNode;
  location: Partial<Location> | string;
  navigationType?: NavigationType;
  navigator: Navigator;
  static?: boolean;
}

it's not clear what the v6 equivalent is; I tried switching to navigator={history} but still got the same error.

To reproduce, copy the following files into a new directory and run npm install then npm test:

package.json

{
  "name": "router6-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "testEnvironment": "jsdom"
  },
  "babel": {
    "presets": [
      "@babel/preset-react"
    ]
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/preset-react": "^7.16.0",
    "@testing-library/react": "^12.1.2",
    "jest": "^27.3.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^6.0.0"
  }
}

index.test.js:

const { render } = require("@testing-library/react");
const { createMemoryHistory } = require("history");
const React = require("react");
const { Router } = require("react-router-dom");

it("used to work", () => {
  render(
    <Router history={createMemoryHistory()}></Router>
  );
});

If you npm install --save-dev react-router@5 and run npm test again, you can see that this passes in v5.

Koslo answered 5/11, 2021 at 21:37 Comment(0)
K
43

React Router v6 splits apart the history into multiple pieces, for this use case the relevant parts are the navigator and the location. This change is hinted at in Use useNavigate instead of useHistory, and you can see it in the definition of the Navigator type used in the Router props:

export declare type Navigator = Omit<History, "action" | "location" | "back" | "forward" | "listen" | "block">;

Just changing history={history} to navigator={history} still left the location prop, from which the router was trying to access the pathname (among other properties), undefined. To get the test working again, update the rendering as follows:

const { render } = require("@testing-library/react");
const { createMemoryHistory } = require("history");
const React = require("react");
const { Router } = require("react-router-dom");

it("works", () => {
  const history = createMemoryHistory();
  render(
    <Router location={history.location} navigator={history}></Router>
  );
});

Note that from React Router 6.4 history is no longer included as a dependency, so if you hadn't explicitly installed it you will need to add it back in.

Koslo answered 5/11, 2021 at 21:37 Comment(1)
This didn't work for me.Collayer
M
5

Just in case someone is stucked on this and can't figure out what is happening after trying all of the solutions posted here, please make sure that you are importing the right Router:

if you have this: import { Router } from "react-router-dom";

change the import for: import { BrowserRouter as Router } from "react-router-dom";

Motherofpearl answered 9/11, 2022 at 18:15 Comment(0)
D
1

This is my solution to the issue, from a course I have been following on Udemy:

const { render } = require("@testing-library/react");
const { createBrowserHistory } = require("history");
const React = require("react");
const { unstable_HistoryRouter: HistoryRouter } = require("react-router-dom");

it("works", () => {
  const history = createBrowserHistory();
  render(
    <HistoryRouter history={history}></HistoryRouter>
  );
});
Drying answered 6/1, 2022 at 8:43 Comment(2)
This was explicitly flagged unstable_ as a "new, undocumented API that will likely introduce bugs". It's probably best not to write code relying on it until it becomes stable.Koslo
Ok thanks, I will make sure to keep that in mind, I did so as I tried your solution, and my URL changed, but those changes were not replicated on the app component, probably due to the SPA nature of the app.Drying

© 2022 - 2024 — McMap. All rights reserved.