Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot read property 'prefetch' and 'push' of null #16864

Closed
kartikag01 opened this issue Sep 5, 2020 · 28 comments · Fixed by #19857
Closed

Cannot read property 'prefetch' and 'push' of null #16864

kartikag01 opened this issue Sep 5, 2020 · 28 comments · Fixed by #19857
Labels
good first issue Easy to fix issues, good for newcomers
Milestone

Comments

@kartikag01
Copy link

kartikag01 commented Sep 5, 2020

Bug report

Cannot read property 'prefetch' and 'push' of null

This bug starts coming when I updated the next version from 9.1.4 to 9.5.2 for both production and dev build.

Describe the bug

Cannot read property 'prefetch' of null
Cannot read property 'push' of null

Uncaught TypeError: Cannot read property 'prefetch' of null
    at prefetch (link.tsx?5e4b:98)
    at eval (link.tsx?5e4b:279)
    at eval (link.tsx?5e4b:60)
    at Array.forEach (<anonymous>)
    at IntersectionObserver.rootMargin (link.tsx?5e4b:51)
link.tsx?5e4b:144 Uncaught TypeError: Cannot read property 'push' of null
    at linkClicked (link.tsx?5e4b:144)
    at onClick (link.tsx?5e4b:314)
    at HTMLUnknownElement.callCallback (react-dom.development.js?61bb:336)
    at Object.invokeGuardedCallbackDev (react-dom.development.js?61bb:385)
    at invokeGuardedCallback (react-dom.development.js?61bb:440)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js?61bb:454)
    at executeDispatch (react-dom.development.js?61bb:584)
    at executeDispatchesInOrder (react-dom.development.js?61bb:606)
    at executeDispatchesAndRelease (react-dom.development.js?61bb:713)
    at executeDispatchesAndReleaseTopLevel (react-dom.development.js?61bb:722)
    at forEachAccumulated (react-dom.development.js?61bb:694)
    at runEventsInBatch (react-dom.development.js?61bb:739)
    at runExtractedPluginEventsInBatch (react-dom.development.js?61bb:880)
    at handleTopLevel (react-dom.development.js?61bb:5803)
    at batchedEventUpdates$1 (react-dom.development.js?61bb:24401)
    at batchedEventUpdates (react-dom.development.js?61bb:1415)
    at dispatchEventForPluginEventSystem (react-dom.development.js?61bb:5894)
    at attemptToDispatchEvent (react-dom.development.js?61bb:6010)
    at dispatchEvent (react-dom.development.js?61bb:5914)
    at unstable_runWithPriority (scheduler.development.js?3069:697)
    at runWithPriority$2 (react-dom.development.js?61bb:12149)
    at discreteUpdates$1 (react-dom.development.js?61bb:24417)
    at discreteUpdates (react-dom.development.js?61bb:1438)
    at dispatchDiscreteEvent (react-dom.development.js?61bb:5881)

To Reproduce

it's coming when Link Tag is used in the Popover element (https://react-bootstrap-v3.netlify.app/components/popovers/)

 <OverlayTrigger
    overlay={(
      <Popover id="popover-saved-item">
        <Link href="/shopping-cart">
          <a><h5>Your Saved Items wishlist</h5></a>
        </Link>
      </Popover>
      )}
  >
Click Popover
  </OverlayTrigger>

Click on

Expected behavior

the error should not be there.

Screenshots

image

System information

  • Version of Next.js: [e.g. 9.5.2]
  • Version of Node.js: [e.g. 10.16.0]

Additional context

When I add prefetch={false} to link then the prefetch error doesn`t come while rendering
but push error is still there on click of the Link

@olaj
Copy link

olaj commented Sep 6, 2020

I had the same issue. But i also had a backlog thing to update my "react-tiny-popover" (from 3 to 5). After i updated it (and fixed the breaking changes) this also seems to have been fixed.

@timneutkens timneutkens added the good first issue Easy to fix issues, good for newcomers label Sep 6, 2020
@timneutkens
Copy link
Member

Sounds like the context value is not passed through correctly in the external component you're using.

@piotrzarycki
Copy link
Contributor

Will fix it :)

@imdongchen
Copy link

I can't reproduce the issue. Seems to work fine https://codesandbox.io/s/cannot-read-property-prefetch-and-push-of-null-16864-5kcj3

@jdjam
Copy link

jdjam commented Sep 10, 2020

I've been getting the same issue since v9.5(.0?) but with using sweetalert2-react-content v3.1.0

@mikerid94
Copy link

mikerid94 commented Sep 10, 2020

I've been getting this error in my react-testing-library tests with v9.5 where I am using in the RouterContext

...
import { render } from '@testing-library/react';
import Router from 'next/router';
import { RouterContext } from 'next-server/dist/lib/router-context';
...
test('when I click the logo, I am taken to the home page', () => {
  const { getByTestId } = render(
    <RouterContext.Provider value={Router}>
      <Header />
    </RouterContext.Provider>,
  );
...
// trigger Link to be clicked -> "Cannot read property 'push' of null" error

I don't have the same error with 9.4.4

@keubs
Copy link

keubs commented Sep 10, 2020

Seeing the same thing as soon as I install Next 9.5.3. For me, calling .simulate('click') on a Next link causes this error, but not on a standard anchor tag. See below:

  it('is a test', () => {
    const testSimulate = jest.fn();

    const link = mount(
      <Link href="/test" as="/test" prefetch>
        <a onClick={testSimulate}>test</a>
      </Link>
    );

    const anchor = link.find('a').at(0);

    act(() => {
      anchor.simulate('click');
    });
    expect(testSimulate).toHaveBeenCalled();
  });

output:

Summary of all failing tests
 FAIL  __tests__/components/utilities/TrackingLink.test.js (10.554s)
   <TrackingLink /> › is a test

    Error: Uncaught [TypeError: Cannot read property 'push' of null]

       97 |
       98 |     act(() => {
    >  99 |       anchor.simulate('click');
          |              ^
      100 |     });
      101 |     expect(testSimulate).toHaveBeenCalled();
      102 |   });

conversely, using a standard anchor tag:

  it('is a test', () => {
    const testSimulate = jest.fn();

    const link = mount(<a onClick={testSimulate}>test</a>);

    act(() => {
      link.simulate('click');
    });
    expect(testSimulate).toHaveBeenCalled();
  });

works fine. I dug into the entire stack trace and can't find a single call to .push(), which is probably the most baffling part to me.

@juliosampaio
Copy link

I've been getting this error in my react-testing-library tests with v9.5 where I am using in the RouterContext

...
import Router from 'next/router';
import { RouterContext } from 'next-server/dist/lib/router-context';
...
test('when I click the logo, I am taken to the home page', () => {
  const { getByTestId } = render(
    <RouterContext.Provider value={Router}>
      <Header />
    </RouterContext.Provider>,
  );
...
// trigger Link to be clicked -> "Cannot read property 'push' of null" error

I don't have the same error with 9.4.4

I'm facing the same issue here @mikerid94

@Clement-Bresson
Copy link

Clement-Bresson commented Sep 29, 2020

I experience the same error TypeError: Cannot read property 'push' of null

Not sure why though. It seems like sometimes router passed to linkClicked function in null here
I will try to dig into it.

[Edit]. I found that the link which triggers the bug, in my case, is located inside a ReactDOM.render (I parse some content and insert some React component in the middle of some elements, maybe I would need to do this differently but that's another story). And it seems inside this rendered component Next context is indeed not passed (or at least the router). Is there a way to manually give the router context for the next link inside to work ? (I found solution below to this question)

[Edit 2] @juliosampaio @mikerid94 I tried to pass the context as you are doing, but found that error is still hapenning because the Context is not the same. So in the link when router is grabbed from useRouter, it's still empty. Using RouterContext from next/dist/next-server/lib/router-context instead of from next-server/dist/lib/router-context worked for me. So edited solution working for me:

...  
import Router from 'next/router';  
import { RouterContext } from  'next/dist/next-server/lib/router-context';  
...  
ReactDOM.render(  
  <RouterContext.Provider value={Router}>  
     <Link /> // nextJS link  
   </RouterContext.Provider>  
 someElement  
)
...

// trigger Link to be clicked -> Working

It might help some people

@mikerid94
Copy link

mikerid94 commented Oct 1, 2020

@Clement-Bresson
Using import { RouterContext } from 'next/dist/next-server/lib/router-context'; ends up with this error for me:

No router instance found.
You should only use "next/router" inside the client side of your app.

Note: I am using import { render } from '@testing-library/react'; instead of ReactDOM.render... from your example.

@Regaddi
Copy link
Contributor

Regaddi commented Oct 1, 2020

I was able to work around the issue by mocking next/link instead of next/router:

jest.mock('next/link', () => ({ children }) => children);

@juliosampaio
Copy link

I experience the same error TypeError: Cannot read property 'push' of null

Not sure why though. It seems like sometimes router passed to linkClicked function in null here
I will try to dig into it.

[Edit]. I found that the link which triggers the bug, in my case, is located inside a ReactDOM.render (I parse some content and insert some React component in the middle of some elements, maybe I would need to do this differently but that's another story). And it seems inside this rendered component Next context is indeed not passed (or at least the router). Is there a way to manually give the router context for the next link inside to work ? (I found solution below to this question)

[Edit 2] @juliosampaio @mikerid94 I tried to pass the context as you are doing, but found that error is still hapenning because the Context is not the same. So in the link when router is grabbed from useRouter, it's still empty. Using RouterContext from next/dist/next-server/lib/router-context instead of from next-server/dist/lib/router-context worked for me. So edited solution working for me:

...  
import Router from 'next/router';  
import { RouterContext } from  'next/dist/next-server/lib/router-context';  
...  
ReactDOM.render(  
  <RouterContext.Provider value={Router}>  
     <Link /> // nextJS link  
   </RouterContext.Provider>  
 someElement  
)
...

// trigger Link to be clicked -> Working

It might help some people

I can confirm it worked for me as well @Clement-Bresson , thanks!

@danbergelt
Copy link

@Clement-Bresson
Using import { RouterContext } from 'next/dist/next-server/lib/router-context'; ends up with this error for me:

No router instance found.
You should only use "next/router" inside the client side of your app.

Note: I am using import { render } from '@testing-library/react'; instead of ReactDOM.render... from your example.

+1, experiencing all of the above issues when combining React Testing Library and the Link component. Having to fall back on Cypress, which is suboptimal for integration tests IMO.

@olaj
Copy link

olaj commented Nov 25, 2020

My tests are failing now for more or less each <Link> in Jest tests that i run against "pages". Just wanted to see if anyone else has starting to see this (again) and having a quick fix.

It happened after an upgrade to 10.0.2 (or 10.0.3), it works fine in 10.0.1. Seems to be alot of talk about prefetching in the changelog so maybe one of those PR is to blame.

To busy to dig into some reproduction at the moment and i will just skip my tests for now, and probably movre these more complicated tests to cypress instead.

@justincy
Copy link
Contributor

@olaj I'm seeing the same. Tests work fine in 10.0.1 but fail with this prefetch null error in 10.0.3.

I'm using react-testing-library. I don't use router-context.

@justincy
Copy link
Contributor

justincy commented Nov 25, 2020

This is the problematic line that my tests are complaining about:

router.prefetch(href, as, options).catch((err) => {

That method is called from here:

prefetch(router, href, as, {

The code was added in this PR: #18904

@justincy
Copy link
Contributor

The solution from @Regaddi to mock next/link fixed my test failures.

@AustinShelby
Copy link

I had a different error message Error: Uncaught [TypeError: Cannot read property 'catch' of undefined] but also from the <Link>.

I managed to fix it by mocking my router like this:

import React, { ReactElement } from "react";
import { render, RenderResult } from "@testing-library/react";
import { NextRouter } from "next/router";
import "@testing-library/jest-dom";
import { RouterContext } from "next/dist/next-server/lib/router-context";

export const mockRouter: NextRouter = {
  basePath: "",
  pathname: "/",
  route: "/",
  asPath: "/",
  query: {},
  push: jest.fn(),
  replace: jest.fn(),
  reload: jest.fn(),
  back: jest.fn(),
  prefetch: jest.fn().mockResolvedValue(undefined),  // This one fixed it for me
  beforePopState: jest.fn(),
  events: {
    on: jest.fn(),
    off: jest.fn(),
    emit: jest.fn(),
  },
  isFallback: false,
};

export const renderWithRouter = (component: ReactElement): RenderResult => {
  return render(
    <RouterContext.Provider value={mockRouter}>
      {component}
    </RouterContext.Provider>
  );
};

and then in my tests, I call renderWithRouter instead of render.

@mikerid94
Copy link

The failing tests that were throwing this sort of error was pinning my project at Nextv9.4 so I made a stripped back repo to cover all the router tests I needed for Nextv10.

  • testing Link component (commit)
  • tests with useRouter (commit)
  • programatically calling Router.push() (e.g. in a redux-saga) (commit)

The router mock I ended up with was very similar to @AustinShelby's:

import { RouterContext } from "next/dist/next-server/lib/router-context";

export const routerMock = {
  basePath: "",
  pathname: "/",
  route: "/",
  asPath: "/",
  query: {},
  push: jest.fn().mockResolvedValue(true),
  replace: jest.fn().mockResolvedValue(true),
  reload: jest.fn(),
  back: jest.fn(),
  prefetch: jest.fn().mockResolvedValue(undefined),
  beforePopState: jest.fn(),
  events: {
    on: jest.fn(),
    off: jest.fn(),
    emit: jest.fn(),
  },
  isFallback: false,
};

...
  render(
    <RouterContext.Provider value={routerMock}>
      <Component />
    </RouterContext.Provider>
  );
...

@gko
Copy link

gko commented Dec 5, 2020

I was able to work around the issue by mocking next/link instead of next/router:

jest.mock('next/link', () => ({ children }) => children);

Thank you for solution. A better way of mocking Link in case you want to check href attribute in tests as well:

jest.mock('next/link', () => {
  const React = require('react');
  const { UrlObject } = require('url');

  type Url = string | typeof UrlObject;
  type LinkProps = {
    href: Url;
    as?: Url;
  };

  return ({ children, href }: React.PropsWithChildren<LinkProps>) =>
    React.cloneElement(React.Children.only(children), { href });
});

FYI as of next 10 as attribute is not necessary anymore.

@hems
Copy link

hems commented Dec 10, 2020

Any ideas on how to solve this issue on StoryBook ??

We have created a separate issue but it issue got closed as duplicated of this issue

Thank You!

@tiaanduplessis
Copy link

@hems updating .storybook/preview.js to the following worked for our use case

import React from "react"; 
import { RouterContext } from  'next/dist/next-server/lib/router-context';  

export const decorators = [
  (Story) => (
    <RouterContext.Provider value={{
      push: () => Promise.resolve(),
      replace: () => Promise.resolve(),
      prefetch: () => Promise.resolve()
    }}>  
      <Story />
    </RouterContext.Provider>
  ),
];

luizeboli added a commit to ufersa/plataforma-sabia that referenced this issue Jan 4, 2021
@see vercel/next.js#16864 for more informations about next Link component breaking in a test environment
@MontoyaAndres
Copy link

same issue as @mikerid94 in version 10.0.1 this error does not exist

@Timer Timer added this to the backlog milestone Jan 6, 2021
@kodiakhq kodiakhq bot closed this as completed in #19857 Jan 6, 2021
@Timer Timer modified the milestones: backlog, iteration 15 Jan 6, 2021
kodiakhq bot pushed a commit that referenced this issue Jan 6, 2021
Fixes #16864 

The `router` can be missing in a test environment when trying to render a `Link` component. This PR bails out of `router.prefetch()` when `router` is missing.

The alternative is for users to mock `next/link` or to mock the `router` and wrap their test components.

Please let me know any feedback.
@eric-burel
Copy link
Contributor

If I get it right, mocking next/router package using a Webpack alias as usual is not enough, because it won't mock the version used by next/link, am I right?
I am not fond of mocking next/link either, that affects stories too much.
So @tiaanduplessis solution is great.

Reading code from the PR #18904 I don't get why it broke suddenly though

@damusnet
Copy link
Contributor

damusnet commented Jan 8, 2021

@eric-burel I don't know about using Webpack aliases.

What happened when it broke is that a feature was introduced that tries to prefetch any internal url as soon as <Link /> is mounted by default. This used to be disabled by default, and the default was switched if I remember correctly. The issue is that in stories and tests, the <Link /> component is used in isolation, outside the Next.js app, and outside of the Router context which provides the prefetch function.

In any case, I submitted a patch #19857 that was just merged this week and is included in the latest release v10.0.5 yesterday, so upgrading to that version should fix it in both tests and stories. Let me know if that is not the case.

@eric-burel
Copy link
Contributor

eric-burel commented Jan 9, 2021

I was using Webpack alias to mock imports from next/router in my app. But since next/link is already built I guess it's too late, the mock is not applied.

I think the blame is not really in Next, but actually in end-user Storybook install. It's perfectly normal to have to setup a decorator to make a framework work (eg loading Material UI theme, adding i18n, loading app global context, etc. etc.)., Next is no exception.

So @tiaanduplessis is not only just a quickfix but actually a decorator we may want to have in our Next/Storybook apps to mock the router in any case.
I've updated #19345 so it can become a part of @next/plugin-storybook. The decorator could also be exposed on its own by Next (loading @next/plugin-storybook creates all sort of issues) from this package for easier adoption.

@eric-burel
Copy link
Contributor

Update for Storybook: you can now use this awesome plugin: https://storybook.js.org/addons/storybook-addon-next-router
It works very well and let's you tweak the router mock using parameters of the story, for easy testing of readyness, query params, links...

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
good first issue Easy to fix issues, good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.