Problem
There was a subtle but critical timing issue in how functional components handled their initial render, which manifested differently depending on their context.
The Core Issue: When a functional component is created, its vdomEffect runs synchronously to generate the initial VDOM. However, the logic to process this VDOM (onEffectRunStateChange) was handled by a subscription that was established after the effect's first run. This meant the component's vdom property was not populated at the end of its constructor.
Symptom A (Nested Components): When a functional component was nested inside a classic container, the parent container needed the child's vdom immediately during its own render cycle. Because the child's vdom wasn't ready, the child would not render correctly on the initial pass.
Symptom B (The Workaround & Double Runs): To fix this, a manual, synchronous call to onEffectRunStateChange() was added to the FunctionalBase constructor. This solved the timing issue for nested components but introduced a new problem: a "double run". The logic would execute once via the manual call and then a second time via the natural (but now redundant) subscription event, causing unnecessary processing.
Solution
The problem was solved by enhancing the Effect class to handle this timing requirement gracefully, removing the need for workarounds in FunctionalBase.
Enhanced Effect Constructor: The Effect constructor in src/core/Effect.mjs was modified to accept an optional third parameter, subscriber.
Pre-emptive Subscription: If a subscriber object is passed during instantiation, the Effect class now immediately subscribes the handler to its isRunning config. This happens before the effect's fn is assigned and the first synchronous run is triggered.
Simplified FunctionalBase:
- The
FunctionalBase constructor in src/functional/component/Base.mjs was updated to pass its onEffectRunStateChange handler directly to the Effect constructor.
- The manual
subscribe() call and the manual onEffectRunStateChange() trigger were both removed.
- A similar manual trigger inside
processVdomForComponents for nested functional components was also removed.
This architectural change ensures that the VDOM processing logic is subscribed before the first run, guaranteeing that it executes exactly once and at the correct synchronous point in the component lifecycle. This fixes the initial render issue for all use cases without causing double runs.
Problem
There was a subtle but critical timing issue in how functional components handled their initial render, which manifested differently depending on their context.
The Core Issue: When a functional component is created, its
vdomEffectruns synchronously to generate the initial VDOM. However, the logic to process this VDOM (onEffectRunStateChange) was handled by a subscription that was established after the effect's first run. This meant the component'svdomproperty was not populated at the end of its constructor.Symptom A (Nested Components): When a functional component was nested inside a classic container, the parent container needed the child's
vdomimmediately during its own render cycle. Because the child'svdomwasn't ready, the child would not render correctly on the initial pass.Symptom B (The Workaround & Double Runs): To fix this, a manual, synchronous call to
onEffectRunStateChange()was added to theFunctionalBaseconstructor. This solved the timing issue for nested components but introduced a new problem: a "double run". The logic would execute once via the manual call and then a second time via the natural (but now redundant) subscription event, causing unnecessary processing.Solution
The problem was solved by enhancing the
Effectclass to handle this timing requirement gracefully, removing the need for workarounds inFunctionalBase.Enhanced
EffectConstructor: TheEffectconstructor insrc/core/Effect.mjswas modified to accept an optional third parameter,subscriber.Pre-emptive Subscription: If a
subscriberobject is passed during instantiation, theEffectclass now immediately subscribes the handler to itsisRunningconfig. This happens before the effect'sfnis assigned and the first synchronous run is triggered.Simplified
FunctionalBase:FunctionalBaseconstructor insrc/functional/component/Base.mjswas updated to pass itsonEffectRunStateChangehandler directly to theEffectconstructor.subscribe()call and the manualonEffectRunStateChange()trigger were both removed.processVdomForComponentsfor nested functional components was also removed.This architectural change ensures that the VDOM processing logic is subscribed before the first run, guaranteeing that it executes exactly once and at the correct synchronous point in the component lifecycle. This fixes the initial render issue for all use cases without causing double runs.