-
-
Notifications
You must be signed in to change notification settings - Fork 617
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(utils): Initial data for atomWithObservable #1058
Conversation
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/pmndrs/jotai/6rmNcNquLaDujuuH2drTj4BF9KN9 |
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. Latest deployment of this branch, based on commit a55f7e3:
|
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, nice to see you work on this!! Please see comments.
src/utils/atomWithObservable.ts
Outdated
@@ -29,69 +29,52 @@ type ObservableLike<T> = { | |||
|
|||
type SubjectLike<T> = ObservableLike<T> & Observer<T> | |||
|
|||
type InitialDataFunction<T> = () => T | undefined | |||
|
|||
export type AtomWithObservableOptions<TData> = { |
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 know it's a bit controversial, but we prefer not exporting types.
export type AtomWithObservableOptions<TData> = { | |
type AtomWithObservableOptions<TData> = { |
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.
sure
src/utils/atomWithObservable.ts
Outdated
export type CreateObservableOptions<TData> = { | ||
initialData?: TData | ||
} |
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 need this. Leftover?
export type CreateObservableOptions<TData> = { | |
initialData?: TData | |
} |
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, accidentally copied that from the previous pr. Thanks for noticing
src/utils/atomWithObservable.ts
Outdated
initialData?: TData | InitialDataFunction<TData> | ||
} | ||
export type CreateObservableOptions<TData> = { | ||
initialData?: TData |
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.
Can we name it initialValue
?
initialData?: TData | |
initialValue?: TData |
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.
Renamed everywhere to initialValue
function firstValueFrom<T>(source: ObservableLike<T>): Promise<T> { | ||
return new Promise<T>((resolve, reject) => { | ||
let resolved = false | ||
const subscription = source.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.
Although this looks cleaner, I think there's a risk of memory leaks.
Such case is, a) we create an atom, b) before getting the first value, c) atom can be unmounted.
We need to unsubscribe this subscription on onMount
cleanup.
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.
@dai-shi hmm, I thought that the suspended component is not mounted before the promise resolves. So, we don't actually have an option to unsubscribe in any case or do I miss something?
To illustrate, here is an atom that suspends and onMount
is never called https://codesandbox.io/s/modern-glade-rutmog?file=/src/App.js:0-413
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.
Yeah, you are right about it. The risk is actually the case onMount
is never called. And, it's not covered with the previous impl either...
In jotai/urql
code, we have a hacky solution with setTimeout
.
jotai/src/urql/atomWithSubscription.ts
Lines 86 to 89 in 2c751b0
let timer: NodeJS.Timeout | null = setTimeout(() => { | |
timer = null | |
subscriptionInRender.unsubscribe() | |
}, 1000) |
Given that this isn't ideal, and this is such a rare case anyway. I think I can merge this PR. Maybe, we leave some comments for the future.
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.
BTW I've already changed other things. Should I do anything else 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.
Let's add a comment like this:
// FIXME this implementation is not fully compatible with concurrent rendering.
// we need to deal with the case `onMount` is not invoked after the atom is initialized.
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.
@dai-shi I've added the comment. Please have a look
- Remove leftovers - Rename initialData => initialValue
…le with concurrent rendering
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.
LGTM
Thanks for your contribution!
#1058 (comment) Issue 1: If we subscribe in render (= Issue 1 is important, but we unsubscribe in the callback (both before this PR and this PR), so it's not a big issue if the callback is invoked soon. So, if Issue 3 is something we care, we would like to revert the change. What do you think? @11bit The |
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.
Wait, there is more important issue.
Issue 4: If the value is changed twice the second value is dropped.
I guess it's the regression with this PR.
So, let's revert it, or come up with a better solution.
Hmm, it's not solved even with the original code without this PR. |
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.
The biggest concern is Issue 4, but it's not a regression of this PR.
Issue 3 is the one introduced in this PR, but only a performance concern.
So, at least, it's mergeable.
So, you mean that current implementation can miss values received between first value and befoe I think solution might be to subscribe only once and do the following:
|
Yes. The previous implementation before this PR can miss them too, but it throws an error in such case. (So, it's sort of a regression?)
"somewhere" will be a variable in the scope, which is fine. |
Reimplementation of initial data option for
atomWIthObservable
inspired by #875 + some refactoring and simplification ofatomWithObservable
code