chicane
chicane copied to clipboard
Type-safe hook for mutating route params
Why it is needed?
I'm trying to create a hook that behaves like useState
for interacting with route params.
Possible implementation
Here's my attempt at an implementation, but I can't figure out why I need the @ts-expect-error
and the as
casting. Is this a limitation with the library or am I missing some typescript stuff?
import { useCallback } from 'react';
import { create } from 'zustand';
import { Router } from '../router';
type EmptyRecord = Record<string | number | symbol, never>;
type AllRoutes = Exclude<ReturnType<typeof Router.useRoute>, undefined>;
type RouteParams = {
[K in AllRoutes as K['params'] extends EmptyRecord
? never
: K['name']]: K['params'];
};
type RouteNames = keyof RouteParams;
export type RouteParamsSetterFn<R extends RouteNames> = (
newValue: RouteParams[R],
opts?: { replace?: boolean },
) => void;
export type UseRouteParamsReturn<R extends RouteNames> = readonly [
RouteParams[R],
RouteParamsSetterFn<R>,
];
export function useRouteParams<const R extends RouteNames>(
route: R,
): UseRouteParamsReturn<R> {
const currentRoute = Router.useRoute<R>([route]);
if (!currentRoute) {
throw new Error('Attempted to use route params on page with unknown route');
}
// It's not clear to me why this 'as' is required here, but it should be safe
const currentParams = currentRoute.params as RouteParams[R];
const setter: RouteParamsSetterFn<R> = useCallback(
(newValue, opts) => {
const { replace = false } = opts ?? {};
if (replace) {
Router.replace<R>(
route,
// @ts-expect-error the params type to .replace isn't happy but can't figure out why
newValue,
);
} else {
Router.push<R>(
route,
// @ts-expect-error the params type to .push isn't happy but can't figure out why
newValue,
);
}
},
[route],
);
return [currentParams, setter];
}