Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react): infer defaultValue as primitive type #223

Merged
merged 4 commits into from
Mar 13, 2023

Conversation

Jinho1011
Copy link
Contributor

defaultValue in useStorageState inferred as a literal type

// const state: 0       Before this PR
// const state: number  After this PR
const [state, setState, refresh] = useStorageState('number', {
  defaultValue: 0
})

This pull request updates the type definition of defaultValue in codebase to be inferred as a primitive type instead of a literal value.

To achieve this, I updated the useStorageState function's generic type parameter to extends Serializable instead of using defaultValue as a Serializable<T>.

Pull Request about #220

PR Checklist

  • I read and included theses actions below
  1. I have read the Contributing Guide
  2. I have written documents and tests, if needed.

@netlify
Copy link

netlify bot commented Mar 10, 2023

👷 Deploy request for slash-libraries pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 02d504e

Comment on lines 19 to 33
export function useStorageState<T extends Serializable>(
key: string
): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void];
): readonly [T | undefined, (value: SetStateAction<T | undefined>) => void, () => void];
export function useStorageState<T>(
key: string,
{ storage, defaultValue }: StorageStateOptionsWithDefaultValue<T>
): readonly [Serializable<T>, (value: SetStateAction<Serializable<T>>) => void, () => void];
): readonly [T, (value: SetStateAction<T>) => void, () => void];
export function useStorageState<T>(
key: string,
{ storage, defaultValue }: StorageStateOptions<T>
): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void];
): readonly [T | undefined, (value: SetStateAction<T | undefined>) => void, () => void];
export function useStorageState<T>(
key: string,
{ storage = safeLocalStorage, defaultValue }: StorageStateOptions<T> = {}
): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void] {
): readonly [T | undefined, (value: SetStateAction<T | undefined>) => void, () => void] {
Copy link
Contributor

@ojj1123 ojj1123 Mar 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jinho1011
First of all, thank you for your interest in my issue.

Is there a reason for putting a generic constraint only in the first function definition? Could it be a mistake?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for letting me know. I fixed the missing one and pushed a new commit with the changes.

@raon0211
Copy link
Collaborator

Thanks for your contribution!

setState(curr => {
const nextValue = typeof value === 'function' ? value(curr) : value;
const nextValue = value instanceof Function ? value(curr) : value;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason a typeof check is replaced with instanceof here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was caused by the mistake of generic constraint only on the first function among the overloading functions.

If generic T is not constrained to extend serializable, T will not be callable, so I modified it like this.

Since I fixed the generic constraint from the above conversation to be added to all overloaded functions, this change can be reversed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! What about reverting this change and making this pull request more focused on the main idea?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted the change as you said. Thank you for taking the time to review my pull request and provide feedback!

Copy link
Collaborator

@raon0211 raon0211 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Thanks, @Jinho1011 !

@raon0211 raon0211 merged commit cbead2a into toss:main Mar 13, 2023
@ojj1123
Copy link
Contributor

ojj1123 commented Mar 13, 2023

@Jinho1011
I was going to review it, but it has already been merged😭

Could you please re-review? @raon0211


This PR didn't solve the issue #220 either:
type-error
(Please check my typescript playground)

In fact, I also tried with a generic constraint(you did) instead of a conditional type constraint(before).

If you insert a type constraint that prevents passing a symbol to defaultValue, the type parameter T is inevitably inferred as a literal type.
This is because primitive types (string, number, and boolean) are included in the type constraint. The reason is explained here #220.

I think that
To prevent inference as a literal type, we need to remove the type constraint that prevents passing a symbol to defaultValue.
Otherwise, we can add a phrase that recommends writing generic to the official documentation:

  const [state, setState, refresh] = useStorageState<number>('number', {
    defaultValue: 0,
  });

What do you think? @Jinho1011 @raon0211

@Jinho1011
Copy link
Contributor Author

@ojj1123

I also confirmed that this PR did not solve the issue. I apologize for my mistake. But I found that the method below widens the type, but what is your opinion on its appearance, even if it is not pretty?

type ToPrimitive<T> = T extends string
  ? string
  : T extends number
  ? number
  : T extends boolean
  ? boolean
  : T extends unknown[]
  ? unknown[]
  : T extends Record<string, unknown>
  ? Record<string, unknown>
  : T;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants