-
-
Notifications
You must be signed in to change notification settings - Fork 622
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
feat(utils): allow providing a subscribe method to createJSONStorage util #2539
feat(utils): allow providing a subscribe method to createJSONStorage util #2539
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this!
The idea seems good. We just need some fixes.
Please take your time.
src/vanilla/utils/atomWithStorage.ts
Outdated
@@ -114,6 +129,42 @@ export function createJSONStorage<Value>( | |||
): AsyncStorage<Value> | SyncStorage<Value> { | |||
let lastStr: string | undefined | |||
let lastValue: Value | |||
|
|||
const webStorageSubscribe: Subscribe<Value> = (key, callback) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still string based, right?
const webStorageSubscribe: Subscribe<Value> = (key, callback) => { | |
const webStorageSubscribe: Subscribe<string> = (key, callback) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before this PR, subscribe method was defined like this:
subscribe?: (
key: string,
callback: (value: Value) => void,
initialValue: Value,
) => Unsubscribe
and the callback would accept value of type Value
. should we change this to accept only strings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey @dai-shi. I finally got some time to work on this. I would appreciate your opinion (and advice) on typing issues.
Regarding this code suggestion, webStorageSubscribe
was previously typed as Subscribe<Value>
and I didn't change the type here. how should we address these typings? Isn't the Value
type always a subset of string
?
src/vanilla/utils/atomWithStorage.ts
Outdated
} | ||
const storageEventCallback = (e: StorageEvent) => { | ||
if (e.storageArea === getStringStorage() && e.key === key) { | ||
callback((e.newValue || '') as Value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems wrong:
callback((e.newValue || '') as Value) | |
callback(e.newValue || '') |
src/vanilla/utils/atomWithStorage.ts
Outdated
type SubscribeHandler<Value> = ( | ||
subscribe: Subscribe<Value>, | ||
key: string, | ||
callback: (value: Value) => void, | ||
initialValue: Value, | ||
) => Unsubscribe | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, we don't need this type alias. (But, not 100% sure.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would something like this be more acceptable?:
const handleSubscribe = (
subscriber: Subscribe<Value>,
...params: Parameters<Subscribe<Value>>
) => {
const [key, callback, initialValue] = params
function callbackWithParser(v: Value) {
let newValue: Value
try {
newValue = JSON.parse((v as string) || '')
} catch {
newValue = initialValue
}
callback(newValue as Value)
}
return subscriber(key, callbackWithParser, initialValue)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I meant to inline it as it's used just once.
src/vanilla/utils/atomWithStorage.ts
Outdated
function callbackWithParser(v: Value) { | ||
let newValue: Value | ||
try { | ||
newValue = JSON.parse((v as string) || '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
v
must be a string always.
newValue = JSON.parse((v as string) || '') | |
newValue = JSON.parse(v || '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you suggest to ensure this?
should we make change the generic types to this:
<Value extends string | null>
or should we use something like JSON.parse(String(v) || '')
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I mean v
must be string
type without any hacks/casts. It feels like there are some lies in types.
I can help for typing later. Please work on other comments first.
src/vanilla/utils/atomWithStorage.ts
Outdated
return () => { | ||
window.removeEventListener('storage', storageEventCallback) | ||
} | ||
if (getStringStorage()?.subscribe) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIR, this can throw. So, we can't call it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the removeEventListener
method won't throw unless the given parameters are wrong which is not happening in our case. am I wrong?
and this code is the same as before:
jotai/src/vanilla/utils/atomWithStorage.ts
Line 166 in a4dc985
window.removeEventListener('storage', storageEventCallback) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, my bad. I was thinking of old implementation. We catch it already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My first comment was actually right. We can't call getStringStorage()
on creation. #2581
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also used this in Next.js but forgot to upgrade and test the new version. this was on me, sorry.
I opened a new PR (#2585) for a possible fix (and a test for detecting this problem) but I would appreciate your opinion.
src/vanilla/utils/atomWithStorage.ts
Outdated
storage.subscribe = handleSubscribe.bind( | ||
null, | ||
getStringStorage()!.subscribe as unknown as Subscribe<Value>, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we use bind
like this in our code base. Please either use an inline function or a higher-order function.
But, restructure will be needed necessary anyway because of the previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
applied a few changes to fix this. I would appreciate a review of them.
Hope you find time to address this. |
Preview in LiveCodesLatest commit: 38bb47e
See documentations for usage instructions. |
@dai-shi I would love to. |
@mhsattarian Hey, I just added a commit to refactor. Please review it. |
src/vanilla/utils/atomWithStorage.ts
Outdated
(typeof window !== 'undefined' && | ||
typeof window.addEventListener === 'function' && | ||
((key, callback) => { | ||
if (!(getStringStorage() instanceof window.Storage)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like we can simply this, if we are allowed to call getStringStorage
in sync.
Hey @dai-shi, sorry for the late response. Are we ready for the merge? |
Cool. I will merge it next week before preparing a new patch release. |
Awesome! since this PR started from syncing atoms with cookies, I will soon open a new PR for an example in the persistence guide. |
I don't remember exactly what I said, but the persistence guide (and other docs) isn't very polished. So, not only adding new things, but also improving the guide entirely would be appreciated. |
Related Issues or Discussions
Related to #1833 (reply in thread)
Summary
previously for providing subscribe functionality to a custom persist storage, one should've used something like the code below which btw was not documented.
it's more convenient to just accept the subscribe method along with
getItem
,setItem
, etc when usingcreateJSONStorage
to create a custom persist functionality like the cookies' in the linked discussed. here is an example:Check List
yarn run prettier
for formatting code and docs