You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have run into cases when working with components with lifecycles asynchronous from each other. This means I run into situations where either I do not receive a signal from the parent so the child component has to create a signal. Later, the parent may update the child and pass it a signal. Now, the issue is how to reconcile these two signals.
Potential workarounds
You can try to newSignal.subscribe(value => {oldSignal.value = value});, but changes to the old signal won't trigger effects on the newSignal. You can try to subscribe back with oldSignal.subscribe(value => {newSignal.value = value}), but I reckon you'd lose out on some batching edge cases, or if equality functions are ever introduced into preact-signals, we might run into issues there.
Suggestion
A naiive approach I've experimented with is creating a .mirror() and .unmirror() method on Signal that modifies the setter and getters of the signal's value.
A potential API could look like this:
consts1=signal(0);consts2=signal(5);constc1=computed(()=>s1.value+s2.value);effect(()=>{console.log('s1: ',s1.value);});effect(()=>{console.log('computed: ',c1.value);});s1.value++;// s1: 1 computed 6s2.value++;// computed: 7s1.mirror(s2);// s1: 6 computed: 12s2.value++;// computed: 14 s1: 7s1.value++;// computed: 16 s1: 8s1.unmirror();// s1: 8 (computed value would not trigger effect due to lack of value change, but computed() runs)s2.value++;// computed: 17s1.value++;// s1: 9 computed: 18
I'm sure this code is garbage and needs a lot more love and a more critical eye, but this is something that I've been messing around with:
Signal.prototype.mirror=function(signal){this._unmirror();if(signal===this){return;}this._mirroredSignal=signal;// ehh there's probably a better way to store/intuit this.this._firstMirroredRun=true;this.value=signal.peek();};Signal.prototype.unmirror=function(){// if there was something previously mirrored, then notify.if(!this._unmirror()){return;}// We need to notify to un-hook the previous mirrored signal// since the getter has changed//// Open question: should we do this at all, or should we go// with an approach that modifies the node list?this._notify();};Signal.prototype._unmirror=function(){this._firstMirroredRun=false;consthadMirror=!!this._mirroredSignal;this._mirroredSignal=undefined;// returns whether something was actually unmirroredreturnhadMirror;};// Pulled this logic out of value's setter since it is reusedSignal.prototype._notify=function(before=()=>false){this._version++;globalVersion++;/**@__INLINE__*/startBatch();// logic to run before notifying. There's probably a cleaner way// to do thisif(before()){endBatch();return;}try{for(letnode=this._targets;node!==undefined;node=node._nextTarget){node._target._notify();}}finally{endBatch();}};Object.defineProperty(Signal.prototype,'value',{get(this: Signal){letsignal=this;constnode=addDependency(this);if(node!==undefined){node._version=this._version;}// if there is a signal that is being mirrored, then just// call that signal's valueif(this._mirroredSignal){signal=this._mirroredSignal;returnsignal.value;}returnthis._value;},set(this: Signal,value){// if this is the first mirrored run, then we need to go and notify// everyone that this signal is now mirroredif(value!==this._value||this._firstMirroredRun){if(batchIteration>100){thrownewError('Cycle detected');}this._value=value;// Run the notifier, but if we have a mirrored signal, then set// that valuethis._notify(()=>{// set the value on the mirrored signal and run its effectsif(this._mirroredSignal){this._mirroredSignal.value=value;}// if this is NOT the first run, then don't notify this signal's nodes// because we do not want to double-call effectsif(this._mirroredSignal&&!this._firstMirroredRun){returntrue;}// If it is the first mirrored run or otherwise, notify all this// signal's nodes. On first run, it would be to register the new// signal value on all of its effects.returnfalse;});this._firstMirroredRun=false;}},});
Pros and Cons of this approach
Pros
fairly straightforward
can undo mirroring
reuses the same signal instance for easy value lookup
Cons
notifies all nodes even if value is unchanged when mirroring or unmirroring
maybe this is right since the location from where the value is coming from has changed but not the exposed value?
There might be something smarter with copying the original signal's nodes and sources, to the mirrored signal, but then picking them back out on unmirroring might prove difficult as well as getting each of the effects to update their oldSignal.value as well as re-stitching any new effects to the mirrored signal.
I'm also not sure how often people will run into this case, or if this case is fundamentally flawed. So an alternative approach would be to not implement this in signals-core, but rather make it possible for users of signals-core to implement this themselves by safely exposing some hooks into the accessors of value
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Issue
I have run into cases when working with components with lifecycles asynchronous from each other. This means I run into situations where either I do not receive a signal from the parent so the child component has to create a signal. Later, the parent may update the child and pass it a signal. Now, the issue is how to reconcile these two signals.
Potential workarounds
You can try to
newSignal.subscribe(value => {oldSignal.value = value});
, but changes to the old signal won't trigger effects on thenewSignal
. You can try to subscribe back witholdSignal.subscribe(value => {newSignal.value = value})
, but I reckon you'd lose out on some batching edge cases, or if equality functions are ever introduced into preact-signals, we might run into issues there.Suggestion
A naiive approach I've experimented with is creating a
.mirror()
and.unmirror()
method onSignal
that modifies the setter and getters of the signal's value.A potential API could look like this:
I'm sure this code is garbage and needs a lot more love and a more critical eye, but this is something that I've been messing around with:
Pros and Cons of this approach
Pros
Cons
Alternative approaches
There might be something smarter with copying the original signal's nodes and sources, to the mirrored signal, but then picking them back out on unmirroring might prove difficult as well as getting each of the effects to update their
oldSignal.value
as well as re-stitching any new effects to the mirrored signal.I'm also not sure how often people will run into this case, or if this case is fundamentally flawed. So an alternative approach would be to not implement this in signals-core, but rather make it possible for users of signals-core to implement this themselves by safely exposing some hooks into the accessors of
value
Beta Was this translation helpful? Give feedback.
All reactions