Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript thinks that useState returns [T | undefined, ...], why?

If I declare a type as the following

type UseBoolean = ReturnType<typeof React.useState<boolean>>;

UseBoolean is inferred to be

[boolean | undefined, React.Dispatch<React.SetStateAction<boolean | undefined>>]

But when I look at the source for React.useState,

function React.useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] 

this would make me think UseBoolean should be

[boolean, React.Dispatch<React.SetStateAction<boolean>>] 

so why isn't it?

like image 932
doliphin Avatar asked Oct 30 '25 12:10

doliphin


1 Answers

But when I look at the source for React.useState...

There's a second overload below it, which is being picked up by your type (but we can fix that, keep reading):

function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

It's there to allow for the case where the user doesn't supply an initial value for the state member. Example (playground link):

import React from "react";

// With no initial value:
const ex1 = React.useState<boolean>();
//    ^? const ex1: [boolean | undefined, React.Dispatch<SetStateAction<boolean | undefined>>]

// With an initial value:
const ex2 = React.useState<boolean>(true);
//    ^? const ex2: [boolean, React.Dispatch<SetStateAction<boolean>>]

I can't think of a way to grab just the first overload, but we can remove the undefined with a mapped type:

type NoUndefinedState<T> =
    T extends [infer S | undefined, React.Dispatch<React.SetStateAction<infer S | undefined>>]
    ? [S, React.Dispatch<React.SetStateAction<S>>]
    : never;

Then:

type UseBoolean = NoUndefinedState<ReturnType<typeof React.useState<boolean>>>;
//   ^? type UseBoolean = [boolean, React.Dispatch<React.SetStateAction<boolean>>]

type UseString = NoUndefinedState<ReturnType<typeof React.useState<string>>>;
//   ^? type UseString = [string, React.Dispatch<React.SetStateAction<string>>]

Playground link

If we want to, we can make it simpler to create these UseXYZ types:

type UseStateTuple<T> = NoUndefinedState<ReturnType<typeof React.useState<T>>>;

Then using it is:

type UseBoolean = UseStateTuple<boolean>;
//   ^? type UseBoolean = [boolean, React.Dispatch<React.SetStateAction<boolean>>]

type UseString = UseStateTuple<string>;
//   ^? type UseString = [string, React.Dispatch<React.SetStateAction<string>>]

Playground link

like image 77
T.J. Crowder Avatar answered Nov 02 '25 03:11

T.J. Crowder



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!