NextRouter was not mounted Next.JS

Using import { useRouter } from "next/router"; as import { useRouter } from "next/navigation"; throws "Argument of type '{ pathname: string; query: { search: string; }; }' is not assignable to parameter of type 'string'."

 const router = useRouter(); const [searchInput, setSearchInput] = useState(""); const search = (e) => { router.push({ pathname: '/search', query: { search: searchInput, }, }) } 

NextJS documentation

Froms docs: "A component used useRouter outside a Next.js application, or was rendered outside a Next.js application. This can happen when doing unit testing on components that use the useRouter hook as they are not configured with Next.js' contexts."

11 Answers

Migrating from the pages directory:

  • The new useRouter hook should be imported from next/navigation and not next/router
  • The pathname string has been removed and is replaced by usePathname()
  • The query object has been removed and is replaced by useSearchParams() router.events is not currently supported.

Here is the solution:

5

NEXT.js 13 changes the package for useRouter.

'use client'; import { useRouter } from 'next/navigation'; export default function Page() { const router = useRouter(); return ( <button type="button" onClick={() => router.push('/dashboard')}> Dashboard </button> ); } 
1
import { useRouter } from "next/router"; 

this still works in pages directory pages

  • App directory Client side

    'use client'; // this works in pages directory as well import { useRouter } from 'next/navigation'; 

If you use import { useRouter } from "next/router" in this directory you get "Error: NextRouter was not mounted"

you have no idea how much time I'm spending on easiest tasks, like getting the dynamic part of my url watch/[videoId]/page.tsx file (app directory).

useRouter will give me:

  • useNavigation instead (isomorphic)
  • useRouter not mounted (using "use client";)

useNavigation has no information about queries/dynamicPaths/paths.

usePathname feels hillarious, then I have to split the path myself (which I'm doing until I understand how to do it correctly.

useSearchParams is empty:

  • getAll() requires a name parameter which makes absolutely no sense IMO, I just want all.
  • get(name) returns NULL

happy .split()'ing though 🍀

\/\/3 LL c o /\/\ e to the bloody edge...

2

As others have pointed out, it is now:

import { usePathname } from 'next/navigation'; const pathname = usePathname(); 

Read more here

1

for next 13 version :

'use client';

import { useRouter } from 'next/navigation';

//for previous version :

'use client';

import { useRouter } from 'next/router';

It still appears the next/router is the suggested way by the official Next.js doc and we are continuing to use it in our projects. So the next/navigation was not the solution for us.

What did work was creating a mock version of the router and injecting that mock via a context. Here is how:

// test-utils/mock-next-router.ts import { NextRouter } from 'next/router'; export const mockNextRouter = (router: Partial<NextRouter>): NextRouter => ({ basePath: '', pathname: '', route: '', query: {}, asPath: '/', back: jest.fn(), beforePopState: jest.fn(), prefetch: jest.fn(), push: jest.fn(), reload: jest.fn(), forward: jest.fn(), replace: jest.fn(), events: { on: jest.fn(), off: jest.fn(), emit: jest.fn(), }, isFallback: false, isLocaleDomain: false, isReady: true, defaultLocale: 'en', domainLocales: [], isPreview: false, ...router, }); 
// __tests__/fluffy-cats-card.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import { describe, expect } from '@jest/globals'; import { RouterContext } from 'next/dist/shared/lib/router-context'; import { mockNextRouter } from '../test-utils/mock-next-router'; import FluffyCatsCard from '../components/fluffy-cats-card'; describe('Fluffy cats card component test', () => { it('should call router.push when click on the "Click me!" button', async () => { const router = mockNextRouter({}); render( <RouterContext.Provider value={router}> <FluffyCatsCard /> </RouterContext.Provider> ); fireEvent.click(screen.getByRole('button', { name: 'Click me!' })); expect(router.push).toHaveBeenCalledWith( { pathname: '/cats', query: { slug: 'fluffy' } }, undefined, {}); }); }); 
// components/fluffy-cats-card.tsx import React from "react"; import { useRouter } from "next/router"; import Image from "next/image"; import fluffyCatsImage from "../public/images/fluffy-cats.jpg"; export const FluffyCatsCard = () => { const router = useRouter(); const showMeFluffyCats = () => { // We don't use next/link for the sake of the example router.push( { pathname: "/cats", query: { slug: "fluffy" } }, undefined, {} ); }; return ( <div className="card"> <Image src={fluffyCatsImage} alt="Fluffy cats" /> <button type="button" onClick={showMeFluffyCats}> Click me! </button> </div> ); }; export default FluffyCatsCard; 

The original idea came from this GitHub repository

It is a bit old but it worked for our case.

Note: Since the router accepts Partial<NextRouter> you can customize the mock however you find fit. Ex. you may use mockNextRouter({ query: { id: 42 }}) to make sure your router will give you the id parameter when you do router.query.id in your component.

I got this error while running the tests and using the app directory with next 13.

I found a workaround using the next-router-mock library.

I have installed it in dev dependencies npm i next-router-mock -D.

In my test's render method, I am using the any type currently because there was no implementation for the refresh method, and I didn't require it yet, but we can workaround by extending the type.

import { AppRouterContext } from 'next/dist/shared/lib/app-router-context' import mockRouter from 'next-router-mock' it('should renders a all questions for survey', async () => { render( <AppRouterContext.Provider value={mockRouter as any}> <FillSurvey params={{ id: '123' }} /> </AppRouterContext.Provider> ) } 

If you want to see the full setup of my project check it here:

import { useNavigate } from 'react-router-dom' function HomeComponents() { const navigate = useNavigate();} return(` <div onClick={() => navigate('/Departments')} />) 

Use Routes in your App.jsx or page.tsx It will work and wont load the server side component

Well in my case changing the import from 'next/router' to 'next/navigation' was not working. A simple server restart fixed it. Keep in mind this hook is not same is 'next/router' hook.

<buton onClick={() => { /* pageRouter.push({ pathName: "/", host; "localhost", port: 3000 }) */ router.push("/", {scroll: true}) }}> Add to cart </button> 
1

Workaround (to be production ready)

Now Nextjs CLI install by default Next@13. If you started with Next@12 and recently reinstalled the packages after, you must remove Next to install a version lower than 13. I don't know why but for me it's what worked.

npm uninstall next npm install [email protected] 

Note: Just be sure next version is under 13

3

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like