pbeshai / use-query-params

React Hook for managing state in URL query parameters with easy serialization.

Home Page:https://pbeshai.github.io/use-query-params

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TypeScript setQuery typing for custom parameter types

glensomerville opened this issue · comments

I have a custom parameter defined as:

const sortableColumns = [
  'name',
  'status',
  'createdDate',
] as const;

type SortBy = typeof sortableColumns[number];

const SortParam = {
  encode: (sortBy: string): SortBy =>
    (sortableColumns as ReadonlyArray<string>).includes(sortBy)
      ? (sortBy as unknown as SortBy)
      : 'createdDate',
  decode: (sortBy: string | (string | null)[] | null | undefined): SortBy =>
    (
      sortableColumns as ReadonlyArray<string | (string | null)[] | null | undefined>
    ).includes(sortBy)
      ? (sortBy as unknown as SortBy)
      : 'createdDate',

When attempting to call setQuery from the useQueryParams hook with the following sorting function:

const [{ sort }, setQuery] = useQueryParams({
    sort: withDefault(SortParam, 'name', false),
  });

const onSortChange = (sort: string) => {
  setQuery({ sort }); // Type error
};

I get the error Type 'string' is not assignable to type '"name" | "status" | "createdDate"'.

This seems to be due to the setQuery function expecting a value of type DecodedValueMap which expects the type of sort to be SortBy. However, I would expect it to accept the type EncodedValueMap when setting the query value allowing for sort to be of type string as expected when passed to the encode function of the custom parameter map.

Is this a bug, or am I perhaps using it wrong?

The expectation is you are working with values in their normal usage form and the library handles the encoding/decoding for you. So you basically always work with a decoded value in your code (outside of creating a custom param). e.g. for an ObjectParam, you'd do:

setQuery({ obj: { foo: 'bar' } })

not

setQuery({ obj: 'foo-bar' })

The same principle applies here.

Also, for your particular use case, you may find createEnumParam handy for generating your SortParam.

Thanks for the clarification. This helps 👍

Using the createEnumParam seems to do the job without so much customisation as you said. However, when I attempt to use it together with withDefault to avoid nulls, the type still contains null | undefined.

e.g. if I use the above example with createEnumParam and withDefault:

sortBy: withDefault(createEnumParam<SortBy>([... sortableColumns]), 'createdDate', false),

the type of sortBy remains 'name' | 'status' | 'createdDate' | null | undefined. I can work around it however.

Perhaps there's a bug with the createEnumParam and withDefault combination though?

Ah yes this is a weird thing I have never figured out how to get the types to work effortlessly with. You need to use as const after your default value:

sortBy: withDefault(createEnumParam<SortBy>([... sortableColumns]), 'createdDate' as const, false),

This will make sortBy have type 'name' | 'status' | 'createdDate' | null (you passed false meaning you want nulls, default is nulls are excluded with withDefault)

Awesome, this solved it! Thanks 👍