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

feat!: make listeners passive, implement all implicit events #158

Merged
merged 2 commits into from
Feb 2, 2025

Conversation

bowheart
Copy link
Collaborator

@bowheart bowheart commented Feb 2, 2025

@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 old instance.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 a ChangeEvent with newState and oldState properties.
  • cycle. Listeners receive a CycleEvent with newStatus and oldStatus properties.
  • invalidate. Listeners receive an InvalidateEvent with no unique own properties (use event.source to access the invalidated node).
  • promiseChange. Listeners receive a PromiseChangeEvent with no unique own properties (use event.source.promise to access the new promise).

All implicit events also have these properties:

  operation?: string // will always be `"on"` for event listeners
  reasons?: EvaluationReason[]
  source?: GraphNode<G>
  type: string // matches the bullets above e.g. "change", "invalidate"

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:

  • When mutating an outer signal, no mutate event is sent to inner signals - they weren't mutated, the outer signal was.
  • When mutating an inner signal, a 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 a batchAtom 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. Introduce scheduler.pre and scheduler.post which function as essentially an ecosystem.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

@bowheart bowheart merged commit c71b4df into master Feb 2, 2025
2 checks passed
@bowheart bowheart deleted the josh/finish-signals branch February 2, 2025 21:25
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.

New Signal Primitive
1 participant