@@ -4,7 +4,18 @@ import { ConcurrentRoot } from 'react-reconciler/constants'
44import create , { UseBoundStore } from 'zustand'
55
66import * as ReactThreeFiber from '../three-types'
7- import { Renderer , createStore , isRenderer , context , RootState , Size , Dpr , Performance } from './store'
7+ import {
8+ Renderer ,
9+ createStore ,
10+ isRenderer ,
11+ context ,
12+ RootState ,
13+ Size ,
14+ Dpr ,
15+ Performance ,
16+ PrivateKeys ,
17+ privateKeys ,
18+ } from './store'
819import { createRenderer , extend , prepare , Root } from './renderer'
920import { createLoop , addEffect , addAfterEffect , addTail , flushGlobalEffects } from './loop'
1021import { getEventPriority , EventManager , ComputeFunction } from './events'
@@ -19,7 +30,6 @@ import {
1930 updateCamera ,
2031 getColorManagement ,
2132 hasColorSpace ,
22- useMutableCallback ,
2333} from './utils'
2434import { useStore } from './hooks'
2535import type { Properties } from '../three-types'
@@ -423,18 +433,19 @@ function unmountComponentAtNode<TCanvas extends Canvas>(canvas: TCanvas, callbac
423433}
424434
425435export type InjectState = Partial <
426- Omit < RootState , 'events' > & {
436+ Omit < RootState , PrivateKeys > & {
427437 events ?: {
428438 enabled ?: boolean
429439 priority ?: number
430440 compute ?: ComputeFunction
431441 connected ?: any
432442 }
443+ size ?: Size
433444 }
434445>
435446
436447function createPortal ( children : React . ReactNode , container : THREE . Object3D , state ?: InjectState ) : JSX . Element {
437- return < Portal children = { children } container = { container } state = { state } />
448+ return < Portal key = { container . uuid } children = { children } container = { container } state = { state } />
438449}
439450
440451function Portal ( {
@@ -456,52 +467,90 @@ function Portal({
456467 const [ raycaster ] = React . useState ( ( ) => new THREE . Raycaster ( ) )
457468 const [ pointer ] = React . useState ( ( ) => new THREE . Vector2 ( ) )
458469
459- const inject = useMutableCallback ( ( rootState : RootState , injectState : RootState ) => {
460- let viewport
461- if ( injectState . camera && size ) {
462- const camera = injectState . camera
463- // Calculate the override viewport, if present
464- viewport = rootState . viewport . getCurrentViewport ( camera , new THREE . Vector3 ( ) , size )
465- // Update the portal camera, if it differs from the previous layer
466- if ( camera !== rootState . camera ) updateCamera ( camera , size )
467- }
470+ const inject = React . useCallback (
471+ ( rootState : RootState , injectState : RootState ) => {
472+ const intersect : Partial < RootState > = { ...rootState } // all prev state props
473+
474+ // Only the fields of "rootState" that do not differ from injectState
475+ // Some props should be off-limits
476+ // Otherwise filter out the props that are different and let the inject layer take precedence
477+ Object . keys ( rootState ) . forEach ( ( key ) => {
478+ if (
479+ // Some props should be off-limits
480+ privateKeys . includes ( key as PrivateKeys ) ||
481+ // Otherwise filter out the props that are different and let the inject layer take precedence
482+ // Unless the inject layer props is undefined, then we keep the root layer
483+ ( rootState [ key as keyof RootState ] !== injectState [ key as keyof RootState ] &&
484+ injectState [ key as keyof RootState ] )
485+ ) {
486+ delete intersect [ key as keyof RootState ]
487+ }
488+ } )
489+
490+ let viewport = undefined
491+ if ( injectState && size ) {
492+ const camera = injectState . camera
493+ // Calculate the override viewport, if present
494+ viewport = rootState . viewport . getCurrentViewport ( camera , new THREE . Vector3 ( ) , size )
495+ // Update the portal camera, if it differs from the previous layer
496+ if ( camera !== rootState . camera ) updateCamera ( camera , size )
497+ }
468498
469- return {
470- // The intersect consists of the previous root state
471- ...rootState ,
472- get : injectState . get ,
473- set : injectState . set ,
474- // Portals have their own scene, which forms the root, a raycaster and a pointer
499+ return {
500+ // The intersect consists of the previous root state
501+ ...intersect ,
502+ // Portals have their own scene, which forms the root, a raycaster and a pointer
503+ scene : container as THREE . Scene ,
504+ raycaster,
505+ pointer,
506+ mouse : pointer ,
507+ // Their previous root is the layer before it
508+ previousRoot,
509+ // Events, size and viewport can be overridden by the inject layer
510+ events : { ...rootState . events , ...injectState ?. events , ...events } ,
511+ size : { ...rootState . size , ...size } ,
512+ viewport : { ...rootState . viewport , ...viewport } ,
513+ ...rest ,
514+ } as RootState
515+ } ,
516+ [ state ] ,
517+ )
518+
519+ const [ usePortalStore ] = React . useState ( ( ) => {
520+ // Create a mirrored store, based on the previous root with a few overrides ...
521+ const previousState = previousRoot . getState ( )
522+ const store = create < RootState > ( ( set , get ) => ( {
523+ ...previousState ,
475524 scene : container as THREE . Scene ,
476525 raycaster,
477526 pointer,
478527 mouse : pointer ,
479- // Their previous root is the layer before it
480528 previousRoot,
481- // Events, size and viewport can be overridden by the inject layer
482- events : { ...rootState . events , ...injectState . events , ...events } ,
483- size : { ...rootState . size , ...size } ,
484- viewport : { ...rootState . viewport , ...viewport } ,
529+ events : { ...previousState . events , ...events } ,
530+ size : { ...previousState . size , ...size } ,
531+ ...rest ,
532+ // Set and get refer to this root-state
533+ set,
534+ get,
485535 // Layers are allowed to override events
486536 setEvents : ( events : Partial < EventManager < any > > ) =>
487- injectState . set ( ( state ) => ( { ...state , events : { ...state . events , ...events } } ) ) ,
488- } as RootState
537+ set ( ( state ) => ( { ...state , events : { ...state . events , ...events } } ) ) ,
538+ } ) )
539+ return store
489540 } )
490541
491- const usePortalStore = React . useMemo ( ( ) => {
492- const store = create ( ( set , get ) => ( { ...rest , set, get } as RootState ) )
493-
542+ React . useEffect ( ( ) => {
494543 // Subscribe to previous root-state and copy changes over to the mirrored portal-state
495- const onMutate = ( prev : RootState ) => store . setState ( ( state ) => inject . current ( prev , state ) )
496- onMutate ( previousRoot . getState ( ) )
497- previousRoot . subscribe ( onMutate )
544+ const unsub = previousRoot . subscribe ( ( prev ) => usePortalStore . setState ( ( state ) => inject ( prev , state ) ) )
545+ return ( ) => {
546+ unsub ( )
547+ usePortalStore . destroy ( )
548+ }
549+ } , [ ] )
498550
499- return store
500- // eslint-disable-next-line react-hooks/exhaustive-deps
501- } , [ previousRoot , container ] )
502551 React . useEffect ( ( ) => {
503- return ( ) => usePortalStore . destroy ( )
504- } , [ usePortalStore ] )
552+ usePortalStore . setState ( ( injectState ) => inject ( previousRoot . getState ( ) , injectState ) )
553+ } , [ inject ] )
505554
506555 return (
507556 < >
0 commit comments