router.query
returns undefined parameter on first render in Next.js
#11484
-
When I have the following code and visit In file import { useRouter } from "next/router"
export default () => {
const router = useRouter()
const { user } = router.query
console.log(user)
return <p>User is {user}</p>
} I will get 2 console logs - first is Can anyone explain to me why is this happening? |
Beta Was this translation helpful? Give feedback.
Replies: 44 comments 94 replies
-
useRouter is a react hook, it catches up to the current query on |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
-
I noticed that other router props are accessible, so you can do some regex magic to get your query param on the first render. const queryKey = 'user';
const queryValue = router.query[queryKey] || router.asPath.match(new RegExp(`[&?]${queryKey}=(.*)(&|$)`)) |
Beta Was this translation helpful? Give feedback.
-
I've created (probably hacky) workaround. If failed to get params from router, I try to get it from function tryToGetParamsFromBrowser(endpoint: string) {
// make sure we don't crash on server side
if (typeof window === 'undefined') {
return null;
}
const path = window.location.pathname;
const paramsInEndpoint: string[] = [];
const endpointMatcher = endpoint
// find each [foo] part and extract 'foo' from it.
.replace(/\[([a-z]+)\]/gi, (_, paramName) => {
// push it to list of params in order they appear
paramsInEndpoint.push(paramName);
// replace [foo] with regexp part that will match actual values
return '([a-zA-Z0-9-]+)';
})
// escpae '/' parts of endpoint so we can use it in regexp
.replace(/\//g, '\\/');
// create regexp that will pick actual values from window path
const endpointRegexp = new RegExp(endpointMatcher, 'ig');
// let's try to match it
const result = endpointRegexp.exec(path);
// if current browser path is not matching - give up
if (!result) {
return null;
}
// we will have actual values as array here in the same order as param names in paramsInEndpoint
const [, ...routeParamsValues] = result;
// let's create a map of params
const paramsFromPath: Record<string, string> = {};
// now, let's connect param names and values and add results to our map
routeParamsValues.forEach((value, index) => {
// index in array of values is the same as index of param name
const paramName = paramsInEndpoint[index];
// add it to params map
paramsFromPath[paramName] = value;
});
return paramsFromPath;
} With this function, on browser side I can call something like
I know it's hacky, but it worked in my use case. |
Beta Was this translation helpful? Give feedback.
-
I'm currently returning nothing as a workaround to solve the empty const router = useRouter()
const { type } = router.query;
if(!type) {
return <></>;
}
// everything else Works fine so far. |
Beta Was this translation helpful? Give feedback.
-
You can specify default const router = useRouter()
const { query = {
q: "",
offset: "0",
limit: 10
} } = router |
Beta Was this translation helpful? Give feedback.
-
If you use a Hook on the first component in the page template, you can return null at the top of your component, as suggest above:
This will work perfectly fine unless you have eslint-plugin-react-hooks set up. It'll flag it with something like: To counter-act this, I ended up wrapping the app in a component as I was dynamically creating the page. Doesn't help with SSR, but someone may find it useful. You could obviously disable that rule too AppWrapper.js:
_app.js
|
Beta Was this translation helpful? Give feedback.
-
The problem I'm having with just returning null when contract is falsey, is what if it actually is undefined? If the initial render shows the query param is undefined, how do you tell between the initial value of undefined and the actual value once the page is hydrated? Also is this only a problem in dev mode? I don't see the initial undefined params being passed once the app is built. |
Beta Was this translation helpful? Give feedback.
-
I use the next hook for that
|
Beta Was this translation helpful? Give feedback.
-
Just to clarify, the "proper" (recommended) solution here is to use |
Beta Was this translation helpful? Give feedback.
-
I did this...
This ensures that the API call isn't made until query id is defined. |
Beta Was this translation helpful? Give feedback.
-
you can use const { query, isReady } = useRouter()
if (!isReady) {
return <Loading />
}
// read `query` |
Beta Was this translation helpful? Give feedback.
-
Is there a reason this wouldn't work out of the box? The query params are available while rendering the page as well, so unless there's an underlying technical reason, I don't see why this isn't filed as a bug? |
Beta Was this translation helpful? Give feedback.
-
I think some other router library like react-router, decided to leave up to the user on how to parse the Personnally I'm using the popular query-string npm package to normalize the parsing
or in functional fashion with Ramda
|
Beta Was this translation helpful? Give feedback.
-
Hello guys, I could solve the undefined first fetch like this: export function useCustomer(id: string) {
const { isReady } = useRouter()
return useSWR(isReady ? [`endpoint/to/customers/${id}`] : null, customersFetcher)
} And then in my component/context I use it like this: const {
query: { customerId }
} = useRouter()
const { data } = useCustomer(customerId as string)
console.log(data) // The customer data is returned and only one fetch is made 😃 I hope it works for you. Happy new year! |
Beta Was this translation helpful? Give feedback.
-
Here's a hook that returns the URL query params as an object ( import { useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/router';
/**
* This hook is used to get and set the URL query parameters using
* next/router.
*
* @see https://github.com/vercel/next.js/discussions/11484
* @see https://nextjs.org/docs/routing/shallow-routing
*/
const useShallowRouting = <T extends Record<string, string | undefined>>(
/**
* The base path of the route. This will be prepended to the route path.
*
* @example
* If URL is "mydomain.com/bills" then "/bills" is the base path.
*/
basePath: string
) => {
const router = useRouter();
const [urlQueryParams, setParams] = useState<Record<string, any>>(
router.query || {}
);
const getUrlQueryParams = useMemo(() => {
const dummyUrl = new URL(`https://example.com${router.asPath}`);
if (dummyUrl.search.length <= 0) return {} as T;
return Object.fromEntries(
new URLSearchParams(dummyUrl.search).entries()
) as T;
}, [router.asPath]);
useEffect(() => {
if (!router.isReady) return;
setParams(getUrlQueryParams);
}, [router.asPath, router.isReady]);
const setUrlQueryParams = (queryObj: Record<string, any>) => {
let queryParamStrings = [];
for (const key in queryObj) {
if (queryObj[key]) {
queryParamStrings.push(`${key}=${queryObj[key]}`);
}
}
const path = `${basePath}?${queryParamStrings.join('&')}`;
router.push(path, undefined, {
shallow: true,
});
};
return [urlQueryParams, setUrlQueryParams] as const;
};
export default useShallowRouting; Usage: const [urlQueryParams, setUrlQueryParams] = useShallowRouting('/bills');
useEffect(() => {
if (urlQueryParams.chamber)
setChamber((urlQueryParams.chamber as string) || '');
if (urlQueryParams.offset)
setOffset(parseInt(urlQueryParams.offset as string, 10) || 0);
memoFetchData({chamber});
}, [urlQueryParams]);
useEffect(() => {
setUrlQueryParams({ chamber, offset });
}, [chamber, offset]);
return(<Component {chamberData} />) |
Beta Was this translation helpful? Give feedback.
-
Hi! What about this ? https://github.com/47ng/next-usequerystate |
Beta Was this translation helpful? Give feedback.
-
Hello, I fix this problem with this method. First add getServerSideProps to your page
Then created useQuery function like this
And always use useQuery for receive query params
And in components like this
|
Beta Was this translation helpful? Give feedback.
-
You need to decide whether you are doing static page or server generated page. For the router to work properly on server, you need to evaluate the page on server. As a result you need to turn the page to SSG. The easiest way is to do this:
|
Beta Was this translation helpful? Give feedback.
-
Here's my workaround. In pages/app component: ...
const [ready, setReady] = useState(false)
const router = useRouter()
useEffect(() => {
if (router.isReady) {
setReady(true)
}
}, [router.isReady])
return ready && <Component {...pageProps} /> For some reason, just |
Beta Was this translation helpful? Give feedback.
-
For anyone wanting to do the import React from 'react';
import { NextRouter, useRouter } from 'next/router';
import { NextComponentType, NextPageContext } from 'next';
export type WithRouterProps = {
router: NextRouter;
};
export type ExcludeRouterProps<P> = Pick<
P,
Exclude<keyof P, keyof WithRouterProps>
>;
export default function withReadyRouter<
P extends WithRouterProps,
C = NextPageContext
>(
ComposedComponent: NextComponentType<C, any, P>
): React.ComponentType<ExcludeRouterProps<P>> {
function WithReadyRouterWrapper(props: any): JSX.Element {
const router = useRouter();
if (!router.isReady) {
return <></>;
}
return <ComposedComponent router={router} {...props} />;
}
WithReadyRouterWrapper.getInitialProps = ComposedComponent.getInitialProps;
// This is needed to allow checking for custom getInitialProps in _app
(WithReadyRouterWrapper as any).origGetInitialProps = (
ComposedComponent as any
).origGetInitialProps;
if (process.env.NODE_ENV !== 'production') {
const name =
ComposedComponent.displayName || ComposedComponent.name || 'Unknown';
WithReadyRouterWrapper.displayName = `withReadyRouter(${name})`;
}
return WithReadyRouterWrapper;
} Which in turn lets you do this. const ReadyComponent = ({ router }) => {
// ... my router.query is ready!
}
export default withReadyRouter(ReadyComponent) |
Beta Was this translation helpful? Give feedback.
-
My solution is to conditionally render the page that needs the variable. (Don't do this if you need to be SEO friendly) import type { NextPage } from "next";
import { useRouter } from "next/router";
import MyPageAsAComponent from './MyPage';
import Loader from './Loader';
const Home: NextPage = () => {
const router = useRouter();
const id = router.query.id; // undefined at first on page load =(
return id ? (
<MyPageAsAComponent id={id} />
) : (
<Loader />
);
}; |
Beta Was this translation helpful? Give feedback.
-
This is how I could make it work using |
Beta Was this translation helpful? Give feedback.
-
is it safe to use |
Beta Was this translation helpful? Give feedback.
-
For anyone using react query, you can use something like this with the enabled flag.
Wanted to share this because quite a few of these suggestions involve conditional rendering which if you have a few hooks in your pages can give you a headache. 🤕 No more 404s! 😁 |
Beta Was this translation helpful? Give feedback.
-
in Next 13
|
Beta Was this translation helpful? Give feedback.
-
I've encountered a challenge while using Next.js with RTK (Redux Toolkit) Query. The issue arises when I attempt to fetch data based on a dynamic route parameter. At the time of the initial render, the router.query is empty which results in calling my query hook with an undefined argument. This, in turn, results in a failed network request. I've tried to use a conditional check to determine when to call the query, but the issue persists. Below is a simplified version of my code to illustrate a solution that I found for this type of problem: import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useMyApiQueryHook } from "../path/to/my/api/hooks";
const useDataFetcher = () => {
const router = useRouter();
const initialParam = router.query.myParam;
// State for storing the fetched data
const [queryData, setQueryData] = useState(null);
// Ensure the argument is string or undefined
const { data } = useMyApiQueryHook(
typeof initialParam === 'string' ? initialParam : undefined,
{ skip: typeof initialParam !== 'string' }
);
useEffect(() => {
if (data) {
setQueryData(data);
}
}, [data]);
return queryData;
}
export default useDataFetcher; Here is the documentation for the RFK library. |
Beta Was this translation helpful? Give feedback.
-
Somehow someway the
|
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
useRouter is a react hook, it catches up to the current query on
ReactDOM.hydrate
.You can read more about it here.