Package Scope
Package name: @toss/react
Describe the bug
tl;dr
- In the example of
useStorageState
, a type error occurs. This is because defaultValue
is inferred as a literal type
rather than a primitive type
.
- I tried to infer the type of
defaultValue
without writing a generic
in useStorageState
, but I couldn't find a way. Please advise if there is any other way
- If there is no other way, I will add a phrase to the document that a
generic
must be created if the defaultValue
is a primitive type
(string, boolean, number).
This is an issue about a type error occurring in the useStorageState
hook.
If you try to use the example in the document of useStorageState
hook as it is, the following type error occurs.
function MyComponent() {
const [state, setState, refresh] = useStorageState('@service/some-resource', {
defaultValue: 0,
});
useEffect(() => {
setState(x => x + 1);
// ^ type error
}, []);
...
}
However, if you write a generic
, type errors do not occur:
const [state, setState, refresh] = useStorageState<number>('@service/some-resource', {
defaultValue: 0,
});
useEffect(() => {
// No error
setState((x) => x + 1);
}, [setState]);
I confirmed that the state
among the return values of useStorageState
in the example above is not inferred as number
, but as literal type 0
. In addition, even when the defaultValue
is a primitive type such as boolean
or string
, it is inferred only as a literal type
:
// const state1: true
const [state1, setState1, refresh1] = useStorageState('boolean', {
defaultValue: true,
});
// const state2: "str"
const [state2, setState2, refresh2] = useStorageState('string', {
defaultValue: 'str',
});
Here, I want to make type inference without writing generic like React.useState
. However, because of the type constraint that prevents the symbol
value from entering the devalutValue
of the useStorageState
argument, type inference is not possible as a primitive type like React.useState
.
// const count: number
const [count, setCount] = useState(1);
// const state: number ✅ What I want
// const state: 0 😢 Actually
const [state, setState, refresh] = useStorageState('number', {
defaultValue: 0
})
In microsoft/TypeScript#10676, when inferring function arguments, type parameters are inferred as widened literal types only in the following situations:
During type argument inference for a call expression the type inferred for a type parameter T is widened to its widened literal type if:
- all inferences for T were made to top-level occurrences of T within the particular parameter type, and
- T has no constraint or its constraint does not include primitive or literal types, and
- T was fixed during inference or T does not occur at top-level in the return type.
In other words, since the primitive type is included in the type constraint of useStorageState
, the type inferred from the type parameter is a literal type:(Please check my playground)
|
export type Serializable<T> = T extends string | number | boolean | unknown[] | Record<string, unknown> ? T : never; |
// useStorageState.ts
// A conditional type constraint contains a primitive type
export type Serializable<T> = T extends string | number | boolean | unknown[] | Record<string, unknown> ? T : never;
...
Therefore, I concluded that the type parameter T
cannot be inferred as a primitive type (e.g. string
, number
or boolean
) because of the type constraint for defaultValue
in useStorageState
. Is there any other way?
If there is no other way, it seems that the phrase that a generic
must be created when a primitive type enters the defaultValue
should be added to the official document.
Please give me some advice on the issue and I'll fix the documentation.
Expected behavior
To Reproduce
Possible Solution
Additional context