diff --git a/packages/fiber/src/core/renderer.ts b/packages/fiber/src/core/renderer.ts index 68ed265438..abeb234545 100644 --- a/packages/fiber/src/core/renderer.ts +++ b/packages/fiber/src/core/renderer.ts @@ -45,6 +45,8 @@ export type BaseInstance = Omit Instance add: (...object: Instance[]) => Instance raycast?: (raycaster: THREE.Raycaster, intersects: THREE.Intersection[]) => void + onUpdate?: (self: Instance) => void + onMount?: (self: Instance) => void } export type Instance = BaseInstance & { [key: string]: any } @@ -272,6 +274,8 @@ function createRenderer(roots: Map, getEventPriority?: ( } } }) + + newInstance.onMount?.(newInstance) } const reconciler = Reconciler({ @@ -301,7 +305,7 @@ function createRenderer(roots: Map, getEventPriority?: ( const localState = (instance?.__r3f ?? {}) as LocalState // https://github.com/facebook/react/issues/20271 // Returning true will trigger commitMount - return !!localState.handlers || !!localState.attach + return !!localState.handlers || !!localState.attach || !!instance.onMount }, prepareUpdate(instance: Instance, type: string, oldProps: any, newProps: any) { // Create diff-sets @@ -352,6 +356,8 @@ function createRenderer(roots: Map, getEventPriority?: ( attach(parent, instance, localState.attach) } } + + instance.onMount?.(instance) }, getPublicInstance: (instance: Instance) => instance, shouldDeprioritizeSubtree: () => false, diff --git a/packages/fiber/src/three-types.ts b/packages/fiber/src/three-types.ts index 1eff384cef..28e64dc607 100644 --- a/packages/fiber/src/three-types.ts +++ b/packages/fiber/src/three-types.ts @@ -30,6 +30,7 @@ export interface NodeProps { ref?: React.Ref key?: React.Key onUpdate?: (self: T) => void + onMount?: (self: T) => void } export type ExtendedColors = { [K in keyof T]: T[K] extends THREE.Color | undefined ? Color : T[K] } diff --git a/packages/fiber/tests/core/renderer.test.tsx b/packages/fiber/tests/core/renderer.test.tsx index 91f90ecc26..cb3d6fad9e 100644 --- a/packages/fiber/tests/core/renderer.test.tsx +++ b/packages/fiber/tests/core/renderer.test.tsx @@ -775,4 +775,35 @@ describe('renderer', () => { const respectedKeys = privateKeys.filter((key) => overwrittenKeys.includes(key) || state[key] === portalState[key]) expect(respectedKeys).toStrictEqual(privateKeys) }) + + it('should safely call onMount', async () => { + let safe = false + + const Test = (props: any) => { + safe = false + return ( + ((self.attached = true), () => (self.attached = false))} + onMount={(self: any) => void (safe = self.attached)} + /> + ) + } + + // Mount + await act(async () => root.render()) + expect(safe).toBe(true) + + // Reconstruct + await act(async () => root.render()) + expect(safe).toBe(true) + + // Update + await act(async () => root.render()) + expect(safe).toBe(false) + + // Unmount + await act(async () => root.render(null)) + expect(safe).toBe(false) + }) })