amannn / next-query-params

Convenient state management of query parameters in Next.js apps
Other
163 stars 12 forks source link

Running Under `NODE_ENV=test` Errors with "NextRouter was not mounted" #40

Open nbibler opened 11 months ago

nbibler commented 11 months ago

Describe the bug

Standing up a new NextJS application, using the Pages router, adding this library, and running under NODE_ENV=test errors immediately with:

Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted

Object.useRouter         in /node_modules/next/dist/client/router.js  (146:15)
exports.NextAdapterPages in /node_modules/next-query-params/dist/NextAdapterPages-b4b86ccd.js (1:293)

This does not appear to be an issue when NODE_ENV=development nor NODE_ENV=production (via npm run build && NODE_ENV=production npm run start.

To Reproduce

  1. npx create-next-app@13.5.6, do not use the App Router and switch into the new directory,
  2. npm install --save next-query-params use-query-params,
  3. Modify the _app.tsx to:

    import '@/styles/globals.css'
    import type { AppProps } from 'next/app'
    +import NextAdapterPages from "next-query-params/pages";
    +import { QueryParamProvider } from "use-query-params";
    
    export default function App({ Component, pageProps }: AppProps) {
    -  return <Component {...pageProps} />
    +  return (
    +    <QueryParamProvider adapter={NextAdapterPages}>
    +      <Component {...pageProps} />
    +    </QueryParamProvider>
    +  );
    }
  4. Run the app under NODE_ENV=test npm run dev

Expected behavior

There should not be a NextRouter error and the application should display the generic NextJS welcome page.

nbibler commented 11 months ago

This error also occurs in NextJS 14.0.1 using the same steps provided above, but with npx create-next-app@latest using the Pages router.

n-d-r-d-g commented 10 months ago

Hi @nbibler,

I was experiencing the same issue. I fixed it by upgrading the following packages:

"next-query-params": "^5.0.0",
"use-query-params": "^2.2.1"

Note: I'm using "next": "14.0.4".

mifrej commented 9 months ago

I'm having this issue even with the above versions mentioned by @n-d-r-d-g

klausbadelt commented 8 months ago

This doesn't just happen with NODE_ENV=test. @n-d-r-d-g 's suggestion doesn't fix. We're on next@14.1.0 and next-query-params@5.0.0

Seems router.isReady should checked before using the router here: https://github.com/amannn/next-query-params/blob/a0472ff80e5e71c42a2d8547bcc20ad7d7c2b814/packages/next-query-params/src/NextAdapterPages.tsx#L14

Our workaround is currently to use a custom wrapper around QueryParamProvider like this:

export const NextQueryParamProvider = ({ children }) => {
  const router = useRouter()

  return router.isReady ? (
    <QueryParamProvider adapter={NextAdapterPages} options={{ removeDefaultsFromUrl: true }}>
      {children}
    </QueryParamProvider>
  ) : children
}

Hopefully we'll have time to submit a PR. Thanks for a very useful library @amannn !

l0gicgate commented 4 days ago

@klausbadelt Your workaround doesn't work in our app unfortunately.

In our app, this only occurs in NODE_ENV=TEST using next 14.2.15

After further investigation, it appears that there's an issue with the way this package is bundled.

The RequireJS version of this package fails in dev. If I simply replace the source from the b4b86ccd bundle with the 293777d9 bundle which is ES6, the error disappears.

The one bundle uses require while the other one uses import. It's the only visible difference.

b4b86ccd bundle uses require (doesn't work): CleanShot 2024-10-15 at 22 05 23@2x

293777d9 bundle uses import (works): CleanShot 2024-10-15 at 22 06 12@2x

CleanShot 2024-10-15 at 21 55 00@2x

For anyone looking for a temporary workaround, you'll need to bring in the pages adapter code from this repo:

import { useRouter } from 'next/router';
import type { ReactElement } from 'react';
import { useMemo } from 'react';
import type { PartialLocation, QueryParamAdapter } from 'use-query-params';
import { QueryParamProvider as UseQueryParamProvider } from 'use-query-params';

const pathnameRegex = /[^?#]+/u;

const Adapter = ({
  children,
  shallow = true,
}: {
  shallow?: boolean;
  children(adapter: QueryParamAdapter): ReactElement | null;
}) => {
  const router = useRouter();
  const match = router.isReady ? router.asPath.match(pathnameRegex) : null;
  const pathname = match ? match[0] : router.asPath;

  const location = useMemo(() => {
    if (typeof window === 'undefined') {
      // On the server side we only need a subset of the available
      // properties of `Location`. The other ones are only necessary
      // for interactive features on the client.
      return { search: router.asPath.replace(pathnameRegex, '') } satisfies Partial<Location>;
    } else {
      // For SSG, no query parameters are available on the server side,
      // since they can't be known at build time. Therefore, to avoid
      // markup mismatches, we need a two-part render in this case that
      // patches the client with the updated query parameters lazily.
      // Note that for SSR `router.isReady` will be `true` immediately
      // and therefore there's no two-part render in this case.
      if (router.isReady) {
        return window.location;
      } else {
        return { search: '' } satisfies Partial<Location>;
      }
    }
  }, [router.asPath, router.isReady]);

  const adapter: QueryParamAdapter = useMemo(() => {
    function createUpdater(routeFn: typeof router.push) {
      return function updater({ hash, search }: PartialLocation & { hash?: string }) {
        void routeFn(
          { pathname: router.pathname, search, hash },
          { pathname, search, hash },
          { shallow, scroll: false },
        );
      };
    }

    return {
      push: createUpdater(router.push),
      replace: createUpdater(router.replace),
      location,
    };
  }, [location, pathname, router, shallow]);

  return children(adapter);
};

export const QueryParamProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => (
  <UseQueryParamProvider adapter={Adapter}>{children}</UseQueryParamProvider>
);