next-router-mock: Cannot use default (singleton) router: "You should only use "next/router" on the client side of your app"

Hello! First of all thanks for awesome package, @scottrippey!

I try to mock next-router with storybook. Is it real to mock default Router as we do it for useRouter?

For instance useRouter works fine:

useRouter example
import { useCallback } from 'react'
import { useRouter } from 'next/router'

const UseRouterPage = () => {
  const router = useRouter()

  const handleClick = useCallback(() => router.push({
      pathname: router?.pathname,
      query: {
        ...router?.query,
        test: '1'
      },
    }, undefined,
    {
      scroll: false,
      shallow: true
    }), [])

  return (
    <main>
      <button onClick={handleClick}>button</button>
    </main>
  )
}

export default UseRouterPage

But with default exported Router, the next error appears:

image

I try use default exported Router because in this case there is no component re-render.

useRouter example
import { useCallback } from 'react'
import Router from 'next/router'

const RouterPage = () => {
  const handleClick = useCallback(() => Router.push({
    pathname: Router?.pathname,
    query: {
      ...Router?.query,
      test: '1'
    },
  }, undefined,
  {
    scroll: false,
    shallow: true
  }), [])

  return (
    <main>
      <button onClick={handleClick}>button</button>
    </main>
  )
}

export default RouterPage

Repo to reproduce: https://github.com/sedlukha/next-router-mock-example

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 21 (11 by maintainers)

Most upvoted comments

@amolagre I had the same ModuleNotFoundError error as you, but it seems to work by importing the next-12 version specifically:

import { MemoryRouterProvider } from "next-router-mock/MemoryRouterProvider/next-12";

Hi @scottrippey , Any update to make next/link (12.2) working with storybook. I’m getting this error while running npm run storybook

ModuleNotFoundError: Module not found: Error: Can't resolve 'next/dist/next-server/lib/router-context' in 'my-project\node_modules\next-router-mock\dist\MemoryRouterProvider'

@scottrippey I use a singleton router for both: read pathname/query and push/replace.

From a Storybook point-of-view, I don’t use <MemoryRouterProvider onPush={...} onReplace={...}> events. I mock the given URL and change it via push/replace inside the story.

My use-case is a complex search page with search results, filters, and a load more button. I write stories with the play function to check how the router changed after filters/pagination manipulations.

And everything works great with useRouter. I can write any test/story and everything will be fine.

But I prefer to use a singleton router to reduce component renders. For instance, let’s take a look at the load more button. When you press it, you want to add a query (e.g. ?page=2) to the URL without the button rerender.

But if you use useRouter, there would be rerendering on every query change.

import { useCallback } from 'react'
import { useRouter } from 'next/router'

// component renders on every useRouter hook changes
const PaginationButton = () => {
  const { query, pathname, push } = useRouter()

  const handleClick = useCallback(() => push({
    pathname: pathname,
    query: {
      ...query,
      page: +query.page + 1
    },
  }, undefined,
  {
    scroll: false,
    shallow: true
  }), [query, pathname, push])

  return (
    <button onClick={handleClick}>load more</button>
  )
}

That’s why I prefer to use a singleton router. In this case, I read query and push only in a callback.

import { useCallback } from 'react'
import Router from 'next/router'

// component renders only once, and use query/pathname/push only in callback
const PaginationButton = () => {
  const handleClick = useCallback(() => Router.push({
    pathname: Router.pathname,
    query: {
      ...Router.query,
      page: +Router.query.page + 1
    },
  }, undefined,
  {
    scroll: false,
    shallow: true
  }), [])

  return (
    <button onClick={handleClick}>load more</button>
  )
}