@@ -52,6 +52,7 @@ export const Compose = (props: ComposeProps) => {
5252 target = { targetFn . original }
5353 decorators = { decorators }
5454 scope = { currentScope }
55+ inherit = { inherit }
5556 />
5657 ) ;
5758} ;
@@ -64,27 +65,42 @@ function ComposeEffects({
6465 store,
6566 target,
6667 decorators,
67- scope
68+ scope,
69+ inherit
6870} : {
6971 store : ReturnType < typeof useCompositionStore > ;
7072 target : any ;
7173 decorators : Decorator < GenericComponent | GenericHook > [ ] ;
7274 scope : string ;
75+ inherit : boolean ;
7376} ) {
7477 const prevRef = useRef < {
7578 decorators : Decorator < GenericComponent | GenericHook > [ ] ;
7679 scope : string ;
7780 } | null > ( null ) ;
7881
82+ // Tracks the decorators currently live in the store as of the last render.
83+ // Updated synchronously during render so it always reflects the most recently
84+ // registered decorators, allowing the atomic swap below to remove the right ones.
85+ const liveRef = useRef < Decorator < GenericComponent | GenericHook > [ ] > ( decorators ) ;
86+
87+ // On re-render with new decorators: atomically replace the old ones in the store
88+ // during render (before React commits). This ensures the store never transiently
89+ // holds both old and new HOCs — which would cause useSyncExternalStore subscribers
90+ // to render with a doubly-wrapped component and mount inner components twice.
91+ if ( liveRef . current !== decorators ) {
92+ store . register ( target , decorators , scope , inherit , true , liveRef . current ) ;
93+ liveRef . current = decorators ;
94+ }
95+
7996 useEffect ( ( ) => {
8097 const prev = prevRef . current ;
8198
82- // On prop change: unregister old decorators.
99+ // On prop change: the render-phase atomic swap already updated the store.
100+ // Emit a non-silent notification now that effects have settled so subscribers
101+ // re-render with the final, clean composition (old HOCs fully gone).
83102 if ( prev && ( prev . decorators !== decorators || prev . scope !== scope ) ) {
84- store . unregister ( target , prev . decorators , prev . scope ) ;
85- // Re-register new ones (they were already registered during render,
86- // but the idempotency check handles this).
87- store . register ( target , decorators , scope ) ;
103+ store . notify ( ) ;
88104 }
89105
90106 prevRef . current = { decorators, scope } ;
0 commit comments