T
TanStack4mo ago
grumpy-cyan

How to write the custom hook 'useStateQuery' similar to Nuqs for '@tanstack/react-router'.

I'm working on implementing a useQueryState hook that is similar in functionality to Nuqs but utilizing @tanstack/react-router for handling query parameters. The implementation involves a Parser utility for parsing and stringifying different data types (string, number, boolean) and a hook that syncs state with query parameters. Here is the current implementation:
import { useRouter, useSearch } from "@tanstack/react-router";
import { useMemo } from "react";

export type Parser<T> = {
parse: (value: string | null) => T | null;
stringify: (value: T | null) => string | null;
};

export const stringParser: Parser<string> = {
parse: (value) => value ?? null,
stringify: (value) => value ?? null,
};

export const numberParser: Parser<number> = {
parse: (value) => (value ? parseFloat(value) : null),
stringify: (value) => (value !== null ? String(value) : null),
};

export const booleanParser: Parser<boolean> = {
parse: (value) =>
value === "true" ? true : value === "false" ? false : null,
stringify: (value) => (value !== null ? String(value) : null),
};

export const useQueryState = <T>(
key: string,
parser: Parser<T>,
defaultValue: T | null = null
): [T | null, (value: T | null) => void] => {
const router = useRouter();
const searchParams = useSearch({ strict: false });

const value = useMemo(() => {
const paramValue = searchParams[key as keyof typeof searchParams];
return parser.parse(paramValue ? String(paramValue) : null) ?? defaultValue;
}, [searchParams, key, parser, defaultValue]);

const setValue = (newValue: T | null) => {
router.navigate({
search: (prev) => {
return {
...prev,
[key]: parser.stringify(newValue),
};
},
});
};

return [value, setValue];
};
import { useRouter, useSearch } from "@tanstack/react-router";
import { useMemo } from "react";

export type Parser<T> = {
parse: (value: string | null) => T | null;
stringify: (value: T | null) => string | null;
};

export const stringParser: Parser<string> = {
parse: (value) => value ?? null,
stringify: (value) => value ?? null,
};

export const numberParser: Parser<number> = {
parse: (value) => (value ? parseFloat(value) : null),
stringify: (value) => (value !== null ? String(value) : null),
};

export const booleanParser: Parser<boolean> = {
parse: (value) =>
value === "true" ? true : value === "false" ? false : null,
stringify: (value) => (value !== null ? String(value) : null),
};

export const useQueryState = <T>(
key: string,
parser: Parser<T>,
defaultValue: T | null = null
): [T | null, (value: T | null) => void] => {
const router = useRouter();
const searchParams = useSearch({ strict: false });

const value = useMemo(() => {
const paramValue = searchParams[key as keyof typeof searchParams];
return parser.parse(paramValue ? String(paramValue) : null) ?? defaultValue;
}, [searchParams, key, parser, defaultValue]);

const setValue = (newValue: T | null) => {
router.navigate({
search: (prev) => {
return {
...prev,
[key]: parser.stringify(newValue),
};
},
});
};

return [value, setValue];
};
Provide right way to do it.
1 Reply
frail-apricot
frail-apricot4mo ago
I have an implementation of this, without the parser stuff because that's already handled by tanstack, but with type-safety

Did you find this page helpful?