#Typescript generics warning

1 messages · Page 1 of 1 (latest)

low trench
#

I am working on a hook here but have one remaining problem.

<T extends Constraint> is flagging a warning.
The warning is: Type parameter 'T' cannot be inferred from any parameter.

Constraint looks like this:

export type Constraint = {
  [key: string]: unknown;
  [index: number]: never;
};

Its implementation looks like the below, and within this code is where the warning is flagged:

import { useAuth0 } from '@auth0/auth0-react';
import { useEffect, useState } from 'react';

import { type Constraint, type Response } from './useFetch.type';

export const useFetch = <T extends Constraint>(
  url: string,
): {
  data: Response<T> | null;
  error: Error | null;
  loading: boolean;
} => {
  const [data, setData] = useState<Response<T> | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(false);

  const { getAccessTokenSilently } = useAuth0();

  useEffect(() => {
    (async () => {
      const accessToken = await getAccessTokenSilently();
      try {
        setLoading(true);
        const response = await fetch(url, {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        });

        if (!response.ok) {
          throw new Error(`HTTP error ${response.status}`);
        }
        const data: Response<T> = await response.json();
        setData(data);
      } catch (error: unknown) {
        setError(error instanceof Error ? error : new Error(String(error)));
      } finally {
        setLoading(false);
      }
    })();
  }, [url, getAccessTokenSilently]);

  return { data, error, loading };
};

Can anyone suggest how to satisfy the warning?

rancid sundial
#

there's not much use in a generic in a function if you only use it once, and here there's not much point to having the generic, and using it would be kinda type-unsafe due to response.json() yielding a Promise<any>

sage parrot
#

You could do:

useFetch<Person[]>('/api/people')
useFetch<string>('/api/people')
useFetch<number>('/api/people')
useFetch<false>('/api/people')
useFetch<null>('/api/people')

Really you could pass literally anything into the generic.

sage parrot
#

Right, but point still stands.

rancid sundial
#

yeah

sage parrot
#

The error isn't coming from TS either, pretty sure TS doesn't do this analysis, so it's probably from another tool like ESLint.

#

But yeah if your generic parameter isn't used anywhere in your arguments, it's very likely a mistake (unless you are doing something unconventional)

sterile rover
#

for unsafe fetches it's a very common pattern

#

response.json and JSON.parse return any, i believe... more importantly, generally you know the exact shape an API will return

sage parrot
#

That's really just hiding a type assertion behind generic

#

It's no more safe than as.

#

Except unlike as, there's no easy way to find all usages of that pattern to audit.

rancid sundial
sterile rover
#

or at least, almost never

sterile rover
#

personally i think it's better to add a comment in one place, than litter an unsafe type assertion all around the codebase

#

not that one is less unsafe than the other, but the way i see it, spamming as Foo:
a) desensitizes you to the danger of casts
b) makes it harder to see the ones that matter when you're doing a grep for unsafeness

sage parrot
#

Yeah so you'd have a useFetch that allows any url and returns unknown, while specialized useFetchPeople that has no argument and returns Person[]

#

Inside useFetchPeople you would do the as Person[], so it's not casting all over your code base.

sterile rover
#

well

rancid sundial
#

the generic over any is just as unsafe as an explicit cast but harder to look for

#

having it be explicit lets you debug for it, no?

sterile rover
rancid sundial
sage parrot
#

You can regex search as, you can't regex search "casting masked as generic"

sterile rover
sage parrot
#

Only if you know what to look for.

sterile rover
#

alternatively: unsafeUseFetch and grep for unsafe

#

and again, usually it's safe because you know the type of the API

rancid sundial
# sterile rover it's easy: just look for `useFetch`

you'd need to remember that useFetch has that unsafe operation
and having useFetch sets a precedent that the pattern is acceptable, what's stopping you or another developer from adding more? then you'd have to memorize all of those

rancid sundial
sterile rover
sage parrot
#

Yeah I do that when I'm lazy and writing an one off tool when correctness isn't the utmost concern, but very much not a good practice.

sterile rover
#

like, you'll have to cast at some point anyway

rancid sundial
rancid sundial
sterile rover
#

and given that the place they'd usually be used is wrapper functions...

rancid sundial
sterile rover
sterile rover
sage parrot
#

If you don't export useFetch so its usage is only limited within one module (which is used by useFetchPeople), that's more acceptable to me.

rancid sundial
sterile rover
#

what's unsafe in types o_O

rancid sundial
rancid sundial
sterile rover
#

casts are runtime though..?

#

and any should be avoided

rancid sundial
sterile rover
#

oh you mean using unsafe to do dangerous runtime operations

rancid sundial
sterile rover
#

🤷 tbh

rancid sundial
#

better examples exist but i don't have them on hand shrugging

#

fair enough

sterile rover
#

it'd be easy to adopt different conventions for type level vs runtime unsafety

#

like unsafe vs dangerously

rancid sundial
sterile rover
#

it should be simple enough to tell developers once, what unsafe and dangerously mean

sage parrot
#

I'd say it's better to just use as, it's known universally as the type level unsafe operation.

sterile rover
#

like, if the normal response to "i have a type error" becomes "oh just slap an as on it"...