feat!: make listeners passive, implement all implicit events #158
+1,699
−693
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.
@affects atoms, immer, machines, react, stores
Description
Introduce the concept of "passive" and "active" event listeners. Make listeners passive by default, which means they don't influence the node's lifecycle. Add the
{ active: true }
ListenerConfig option for explicitly influencing lifecycle changes (this mimics the oldinstance.addDependent
method - while the active listener is attached, it prevents automatic destruction. When removed, Zedux checks the ref count of the node and schedules destruction if needed).Implicit Events
Finish implementing all implicit event types. They are officially:
change
. Listeners receive aChangeEvent
withnewState
andoldState
properties.cycle
. Listeners receive aCycleEvent
withnewStatus
andoldStatus
properties.invalidate
. Listeners receive anInvalidateEvent
with no unique own properties (useevent.source
to access the invalidated node).promiseChange
. Listeners receive aPromiseChangeEvent
with no unique own properties (useevent.source.promise
to access the new promise).All implicit events also have these properties:
Explicit Events
Test and handle tons of edge cases around the
mutate
event. It's going to be much less common to attach listeners to inner signals than outer signals (or the whole atom) for relaying transactions e.g. to other realms or data grids. Because of this:mutate
event is sent to inner signals - they weren't mutated, the outer signal was.mutate
event is sent to the outer signal. This mutate event's transactions are modified to include the key path to the inner signal.mutate
is the only explicit event.batch
is scrapped. It isn't possible to set a node's state and defer running its dependents. That was possible with stores since stores weren't graph nodes, but it still wasn't good - the store's state would be temporarily, visibly out of sync with its dependent atom until the scheduler ran on the next tick. This state tearing is not something we can allow in the atom graph at all - Zedux's atomic model is glitchless.We could defer callback functions before allowing
.set
calls etc to update any node state. But it's such a rare use case and very easy for users to create abatchAtom
that does that if needed - stores callback functions and runs them later. Since it's rarely used and easy to do manually or via an addon, Zedux should not include this functionality in its core.Other Stuff
Finally(!) fix scheduler flushes in potentially recursive
.set
,.mutate
,handleStateChange
,.send
calls. Introducescheduler.pre
andscheduler.post
which function as essentially anecosystem.batch()
call without the need to create a closure. This fixed lots of new edge cases introduced with recursive mapped signals.Guarantee listener call order based on graph node weight and order added. Inner signal listeners always run before outer signal listeners. Multiple listeners on the same node and event type (or different event types that are sent together) run in the order they were added in.
Add tests for complicated nesting configurations involving atoms and signals in other atoms and signals. Add tests for all implicit event types.
Issues
Resolves #115