Reactive Pairing Fixes
Reactive Paring fixes
Written for AI agents. See Log Methodology Note below for details.
Once implementing Reactive Pairing we started seeing anomalies in Jay that Reactives are flushed twice and when not needed. This exposed a few problems and a design flaw with how Reactive Pairing works.
Reactive Pairing is the process at which when a reaction of reactive A is reading a signal from reactive B,
once reactive B is flushed it will also flush reactive A to ensure it reruns any reactions that depend on B's signal.
Reactive Pairing makes a lot of sense when A is a component reactive and B is a context reactive - at which point
we want all the component using the context signals to rerun reactions and potentially re-render.
The problem happens in two cases -
Ais a parent component,Ba child component. duringA's element render function (run as a reaction ofA), we also run the child component constructor. Any signal read fromBnot in a reaction ofB(but in the constructor) will trigger pairing such that when thatBsignal updates,Awill flush.Ais a parent component,Ba child component. WhenBemits an event using an effect (which is a reaction ofB) whenAhandling the event reads any ofAsignal values it triggers pairing such that whenAsignal updates,Bwill flush.
The root cause is that Reactive Pairing is based on function call stack - when reading a signal of B within a reaction of A
we create pairing. This rule is way to generic, yet has big advantage for the component - context case - no need to
introduce a new API for the component to track context changes.
Aside fixes as a result of the investigation
Investigating this issue exposed a few other issues:
Patch creating new objects when patching same values
the jay-json-patch library patch function used to always create new objects, even if the patch did not update
any value. Because Jay is using immutable objects, it caused the system to think there is a change, while there is none.
Consider the object
const obj = { a: 1, b: 2, c: 3 };
Applying a patch such as
const thePatch = [
{ op: REPLACE, path: ['b'], value: 2 },
{ op: REPLACE, path: ['c'], value: 3 },
];
does not change the object, and those patch now returns the original object
expect(patch(obj, thePatch)).toBe(obj);
createDerivedArray
The createDerivedArray hook in jay-component used to be implemented using a Reactive for each mapped item.
This implementation was subject to the Reactive pairing issue above, as well as sub-optimal.
It is now implemented in a simpler and more efficient way.
component mount
Component mount function did not check if the component is mounted, and could have run twice in some cases.
We have changed component mount to be a signal with the actual mount / unmount logic a reaction, to ensure more consistent behaviour
Fixing Reactive Pairing
It is clear that using only function call stack the reactive pairing is way too aggressive.
- We want reactive pairing between context (providing signals) and components (reading signals).
- We want reactive pairing between context (providing signals) and other contexts (reading signals).
- We do not want components to provide signals for reactive pairing.
option 1
We can extend Reactive with Reactive Pairing configuration such that
- Components will be configured as (reading signals)
- Contexts will be configured as (reading or providing signals)
This option can still create unexpected pairings
option 2
Explicit pairing API, such that A will have to enable tracking B's state.
Can we create such an API that will not pollute the usage of context API? probably.
one way of doing so is to have an API A.enableReadingfrom(B) which means that A will now track signals of B.
This is the preferred way, as it is not too eager, will likely not cause unexpected issues and also means
A points at B, and given A is a component and B a context, A is more likely to be cleared. Pointing B to A
can cause a memory leak, but the reverse is less likely to.
Log Methodology Note
Note: These design logs are written primarily for AI agents as part of the Design Log methodology and made accessible here for human readers. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation.