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.
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> ); } 1import { 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...
2As others have pointed out, it is now:
import { usePathname } from 'next/navigation'; const pathname = usePathname(); Read more here
1for 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> 1Workaround (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