-
Notifications
You must be signed in to change notification settings - Fork 3
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
[Breaking] feat: synchronous atomEffect #62
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
4a426e8
to
93d20dd
Compare
93d20dd
to
7e69c18
Compare
6ac733b
to
792e24d
Compare
231d80e
to
0cb3cbb
Compare
0cb3cbb
to
9679054
Compare
1 task
cc001f0
to
418b1d1
Compare
418b1d1
to
4227097
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR updates the behavior of
atomEffect
. In the past,atomEffect
would run in the next microtask; now, it runs synchronously on mount and whenever its dependencies change. Batching is still supported when a writable atom updates multiple dependencies: those related state changes are batched together, and the effect only runs after the writable atom finishes its updates. This prevents partial updates and provides a more predictable, centralized way to handle downstream changes.Example Usage
When
someAtom
is updated, the effect runs immediately and updatesanotherAtom
synchronously in the same task or microtask. This helps keep application state consistent and avoids race conditions.When multiple atom dependencies are updated in a writable atom, atomEffect runs synchronously after all updates have been processed. This ensures atomEffect continues to support batching behavior. Please see the example below.
Migrating to v2
For most cases, you won't need to do anything.
Please keep reading if your logic relies on running the effect with a microtask delay or on batching updates.
Adding back the microtask delay
before
after
Batching updates
The atomEffect V2 only batches updates inside a writable atom.
before
after
Description of Operation
atomEffect
uses two atomsThe ref atom stores the state needed for use inside the effect atom's read function, which includes:
atomState.d
is cleared inreadAtomState
, and the effect effect does not run duringreadAtomState
, it's necessary to "remind" Jotai of these dependencies by callingget
on each. Storing them separately fromatomState
addresses this need.atomState
of the effect atom. Itsepoch
is incremented to force Jotai to run theonAtomChange
hook whenever the effect atom's dependencies change.When the effect atom is subscribed, its
atomState
is created for the first time, causingonInit
to fire. A significant portion of the complexity in atomEffect resides in this hook.onInit
provides the scope where therunEffect
function and several state variables are declared (details onrunEffect
below).Within
onInit
, several store "building blocks" are also extracted. These are used to create a custom getter and setter, as well as to inoke internal functions necessary for atomEffect's operation. For example,ensureAtomState
retrieves the effect atom'satomState
.The
ensureSyncEffect
function checks if thesyncEffectChannel
- a set ofrunEffects
andrunCleanups
- has already been created for the store. This channel is where effects and cleanups are queued for execution inonStoreFlush
.From the store, we also get
storeHooks
, which support adding hooks foronAtomChange
,onAtomMount
,onAtomUnmount
, andonStoreFlush
. These hooks are used to addrunEffect
(on atom mount or change), orrunCleanup
(on atom unmount) to thesyncEffectChannel
, which will be invoked inonStoreFlush
. Currently, there is no mechanism to remove the listeners from these store hooks.The
runEffect
functionrunEffect
is responsible for executing the effect and managing both synchronous and asynchronous operations for the custom getter and setter. It returns early in several edge cases (discussed later). On each run, dependencies are cleared so they can be reassessed for that run.Custom getter and setter are needed because:
Custom Getter
peak
method on the getter is juststore.get
, which does not add atom dependencies.Custom Setter
inProgress
counter to prevent infinite loops when setting atoms that are themselves (or whose dependents are) included among the effectAtom’s dependencies. Any such recursion is blocked by an early return inrunEffect
.recurse
method on the setter wraps the setter and then synchronously calls runEffect if the effect atom has changed. This recursion is intentional, and developers are responsible for avoiding infinite loops.hasChanged
is set totrue
when theatomOnChange
storeHook runs (which occurs synchronously in the setter). Thus, by the time thefinally
block is reached,hasChanged
will betrue
.recomputeInvalidatedAtoms
call in thefinally
block ensures that Jotai’s atom graph is updated according to the change beforerunEffect
is invoked again.The
runCleanup
functionBefore the effect is run,
runCleanup
may optionally be invoked.runCleanup
independently manages theisSync
variable to ensure the custom getter and setter run in synchronous mode during cleanup.The
effect
PropertyThe effect is assigned as a property on the effect atom. This allows developers to change the effect of an effect atom after it has been initialized. The effect receives the custom getter and setter as parameters and may return a cleanup function. If a cleanup function is returned, it is passed to a new
runCleanup
call.Unmounting
The Effect will continue to be responsive until the atomEffect unmounts. Onunmount the unmount
storeHook
runs. IfrunCleanup
is a function, it gets added to thesyncEffectChannel
, to be processed influshCallbacks
.This concludes the description of the operation of
syncEffect
.