73

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."

Abhimanyu Sharma
  • 858
  • 1
  • 9
  • 17
thewb
  • 849
  • 1
  • 4
  • 12

8 Answers8

192

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: https://beta.nextjs.org/docs/api-reference/use-router

ivo
  • 1,936
  • 1
  • 5
  • 3
  • 1
    Specifically, you need to track uses of "next/router" from a component in "app" folder. This issue starts appearing somewhere after 13.0.1 (I hit it with current 13.0.4). This means progressive migration from pages to app is not easy if your components depends on next/router, you might want to migrate all at once. – Eric Burel Nov 23 '22 at 08:42
  • It perfectly works. If you're to get query params in pages, `searchParams` prop of page component is the best choice for you. `export default function Page({ children, searchParams }: { children?: React.React Node, searchParams?: { paramA?: string } }) { ... }` – tronx.dev Dec 07 '22 at 18:18
  • how do you push query options? It says: `Argument of type '{ pathname: string; query: any; }' is not assignable to parameter of type 'string'.ts` – Shivam Sahil Feb 28 '23 at 02:37
  • 1
    well, `searchParams` will be the right choice, right? afaik it's still under active development: https://github.com/vercel/next.js/pull/46205/files – faebster Mar 14 '23 at 15:15
  • Make sure to use the correct import `next/navigation` when using Next 13 with app directory. – Alexander Jul 30 '23 at 13:23
27

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>
  );
}
Nditah
  • 1,429
  • 19
  • 23
  • this worked, I am actually trying to implement useRouter with next experimental not at all working this small thing worked – ullas kunder May 04 '23 at 16:57
17
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"

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
7

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...

faebster
  • 727
  • 7
  • 13
  • `getAll` doesn't mean all query attributes but an array of all items with the same name. Like `?id=1&id=2&id=3&action=edit` we could have `sp.getAll('id')`. I guess you are looking for `keys` or `entries` – Falci May 25 '23 at 10:10
  • this answer explain how you can get id, https://stackoverflow.com/a/74574345/2877427 – AnasSafi Aug 15 '23 at 11:07
4

As others have pointed out, it is now:

import { usePathname } from 'next/navigation';

const pathname = usePathname();

Read more here

Edgar Quintero
  • 4,223
  • 2
  • 34
  • 37
  • as described this gives me the whole pathname, then I have to `pathname.split("/").pop()` to get my dynamic part? There is another approach I assume in page/ directory I could useRouter().query, then I got my id ready to use (no string editing) it's at least what I expect from a framework like Next.js – faebster Mar 14 '23 at 19:40
1

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 https://github.com/bmvantunes/youtube-react-testing-video9-nextjs-router

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.

nyxz
  • 6,918
  • 9
  • 54
  • 67
0
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

-3

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 next@12.2.0

Note: Just be sure next version is under 13

faebster
  • 727
  • 7
  • 13
Luccin
  • 219
  • 2
  • 7
  • That makes sense now, as the problem seemed to start when I migrated to 13. It is unfortunate that there is no other way than downgrading NextJS – thewb Nov 13 '22 at 20:03
  • Have you followed all steps for upgrading to next@13? https://nextjs.org/docs/upgrading – Luccin Nov 13 '22 at 20:27
  • I did. Apart from this the only other problem I had after migrating was TailwindCSS not working but solved that quickly – thewb Nov 13 '22 at 20:46