SearchParams for instant states #56399
Replies: 5 comments 2 replies
-
I encountered this issue when building Your example would then look like this: import { useQueryState, parseAsInteger } from 'nuqs'
function QueryButton() {
const [state, setState] = useQueryState('query', parseAsInteger.withDefault(0))
return (
<div
onClick={() => setState(x => 1 - x)} // toggle between 0 and 1
className={`${
state ? "bg-red-500" : "bg-blue-500"
} p-3 rounded-md text-white cursor-pointer`}
>
Query button
</div>
);
} |
Beta Was this translation helpful? Give feedback.
-
Nice solution @franky47! I ended with a similar approach: import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useMemo, useState, useEffect } from 'react';
type toString = {
toString(): string;
};
export type SetQueryParam<T extends toString> = (
value: T,
// Set it true on the last param update, and false when you need to update multiple params at once
updateUrl?: boolean,
// if prevParams is provided, it will use it instead of current url params
// this is useful when you want to update multiple params at once
prevParams?: URLSearchParams
) => URLSearchParams;
/**
* Hook for replace useState with sync value with url.
* It uses useState internally to make changes instantly,
* but also syncs with url in case url is changed (back button clicked, or url changed manually)
**/
export function useQueryParam<T extends toString>(key: string, validate: (value: string | null) => T) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const queryValueRaw = searchParams.get(key);
// validate query value
const queryValue = useMemo(() => validate(queryValueRaw), [queryValueRaw, validate]);
const [value, setValue] = useState<T>(queryValue);
const valueStr = useMemo(() => value.toString(), [value]);
const setQueryParam: SetQueryParam<T> = (newValue, updateUrl = true, prevParams?) => {
const newValueStr = newValue.toString();
const params = prevParams || new URLSearchParams(searchParams.toString());
if (newValueStr !== queryValueRaw) {
setValue(newValue);
params.set(key, newValueStr);
if (updateUrl) {
router.push(pathname + '?' + params.toString(), { scroll: false });
}
}
return params;
};
// sync value with url, in case url is changed (back button for example)
useEffect(() => {
if (queryValueRaw !== valueStr) {
setValue(queryValue);
}
// Don't sync when state value is changed, it's a one way sync
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryValue, queryValueRaw]);
return [value, setQueryParam] as const;
} And an example of usage with Zod: export const SortDirectionSchema = z.enum(['asc', 'desc']).catch('asc');
export type SortDirection = z.infer<typeof SortDirectionSchema>;
//...
const [sortDirection, setSortDirection] = useQueryParam('sortDirection', (value) => SortDirectionSchema.parse(value)); |
Beta Was this translation helpful? Give feedback.
-
Thanks guys, my solution for now was to add a state to mark when the url is updated |
Beta Was this translation helpful? Give feedback.
-
There are two solutions if you want to get instant states when modifying search params:
|
Beta Was this translation helpful? Give feedback.
-
Created a library for this - https://github.com/asmyshlyaev177/state-in-url , can store complex state and enjoy TS autocomplete. export const countState = { count: 0, other: { arr: [1,2,3 ] } }
...
import { useUrlState } from 'state-in-url/next';
import { countState } from './countState';
function MyComponent() {
const { state, updateState, updateUrl } = useUrlState(countState);
...
<button onClick={() => updateUrl({ count: state.count + 1 }) }>
// or <button onClick={(curr) => updateUrl({ ...curr, count: curr.count + 1 }) }>
Increment
</button> |
Beta Was this translation helpful? Give feedback.
-
Hi,
It's evident that UI states often need to be reflected in the URL as well.
The issue is that useRouter and useSearchParams do not change state instantly,
unlike useState. (I've made an example on codesandbox if you have doubts about this. Try to enable Network Throttling in your browser).
It appears that NextJs reloads the page when the URL changes, and sometimes it's unnecessary,
because the state has already changed.
How can I change the URL without requesting the page again? And still be able to use useSearchParams or any other reactive replacement for SearchParams?
Beta Was this translation helpful? Give feedback.
All reactions