chicane icon indicating copy to clipboard operation
chicane copied to clipboard

Type-safe hook for mutating route params

Open benlongo opened this issue 10 months ago • 3 comments

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];
}

benlongo avatar Apr 22 '24 19:04 benlongo