Typescript error when use react useState hook with generics

Viewed 132

When I use generic type in react useState hook, I have a weird error:

// From react typings
type SetStateAction<S> = S | ((prevState: S) => S);
type Dispatch<A> = (value: A) => void;
declare function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];


// Declare generic response type
type TDataResponse<T> = { type: 'success'; data: T; };
type TErrorResponse = { type: 'error'; error: string; };
type TResponse<T> = TDataResponse<T> | TErrorResponse;

function loadData<T, U = TResponse<T>>() {
    const [data, setData] = useState<U | null>(null);

    // ERROR: Argument of type '{ type: string; error: string; }' is not assignable to parameter of type 'SetStateAction<U | null>'.
    // Object literal may only specify known properties, and 'type' does not exist in type '(prevState: U | null) => U | null'.(2345)
    setData({ type: 'error', error: 'error message'});
}

But if use useState<TResponse<T> | null> there is no error:

function loadData2<T, U = TResponse<T>>() {
    const [data2, setData2] = useState<TResponse<T> | null>(null);

    // No error
    setData2({ type: 'error', error: 'error message'});
}

Typescript Playground

1 Answers

it's not weird at all - in given syntax (function loadData<T, U = TResponse<T>>() {...}) the U type can be "anything" - so it's not possible for TypeScript to know what may it be during the runtime as only U sub-types are assignable to it, and the { type: 'error', error: 'error message'} is not a sub-type of U

it won't get any better if you will change the definition to function loadData<T, U extends TResponse<T> = TResponse<T>>() {...}, because situation is the same, the U type just got narrower though

in situation like this one you can't use generics for internal values (so the correct definition would be just function loadData<T>() {...}), unless you somehow get the U type from somewhere, eg.

function loadData<T, U = TResponse<T>>(loader: () => U) {
  const [data, setData] = useState<U | null>(null)

  setData(loader());
}

loadData<number>(() => ({ type: 'error', error: 'it works, lol' }))
Related