diff --git a/packages/fiber/__mocks__/react-native.ts b/packages/fiber/__mocks__/react-native.ts index 129eed47d5..a43031723c 100644 --- a/packages/fiber/__mocks__/react-native.ts +++ b/packages/fiber/__mocks__/react-native.ts @@ -1,27 +1,25 @@ import * as React from 'react' -import { ViewProps, LayoutChangeEvent } from 'react-native' +import { ViewProps, LayoutChangeEvent, View as RNView } from 'react-native' // Mocks a View or container as React sees it -const Container = React.memo( - React.forwardRef(({ onLayout, ...props }: ViewProps, ref) => { - React.useLayoutEffect(() => { - onLayout?.({ - nativeEvent: { - layout: { - x: 0, - y: 0, - width: 1280, - height: 800, - }, +const Container = React.memo(({ onLayout, ...props }: ViewProps) => { + React.useLayoutEffect(() => { + onLayout?.({ + nativeEvent: { + layout: { + x: 0, + y: 0, + width: 1280, + height: 800, }, - } as LayoutChangeEvent) - }, [onLayout]) + }, + } as LayoutChangeEvent) + }, [onLayout]) - React.useImperativeHandle(ref, () => props) + // React.useImperativeHandle(ref, () => props) - return null - }), -) + return null +}) export const View = Container export const Pressable = Container diff --git a/packages/fiber/src/native/Canvas.tsx b/packages/fiber/src/native/Canvas.tsx index a3ae0752de..13e825338b 100644 --- a/packages/fiber/src/native/Canvas.tsx +++ b/packages/fiber/src/native/Canvas.tsx @@ -14,6 +14,7 @@ const _View = View as any export interface CanvasProps extends Omit, 'size' | 'dpr'>, Omit { children: React.ReactNode style?: ViewStyle + ref?: React.Ref } export interface Props extends CanvasProps {} @@ -22,13 +23,81 @@ export interface Props extends CanvasProps {} * A native canvas which accepts threejs elements as children. * @see https://docs.pmnd.rs/react-three-fiber/api/canvas */ -const CanvasImpl = /*#__PURE__*/ React.forwardRef( - ( - { - children, - style, +function CanvasImpl({ + ref, + children, + style, + gl, + events = createTouchEvents, + shadows, + linear, + flat, + legacy, + orthographic, + frameloop, + performance, + raycaster, + camera, + scene, + onPointerMissed, + onCreated, + ...props +}: Props) { + // Create a known catalogue of Threejs-native elements + // This will include the entire THREE namespace by default, users can extend + // their own elements by using the createRoot API instead + React.useMemo(() => extend(THREE as any), []) + + const Bridge = useBridge() + + const [{ width, height, top, left }, setSize] = React.useState({ width: 0, height: 0, top: 0, left: 0 }) + const [canvas, setCanvas] = React.useState(null) + const [bind, setBind] = React.useState() + React.useImperativeHandle(ref, () => viewRef.current) + + const handlePointerMissed = useMutableCallback(onPointerMissed) + const [block, setBlock] = React.useState(false) + const [error, setError] = React.useState(undefined) + + // Suspend this component if block is a promise (2nd run) + if (block) throw block + // Throw exception outwards if anything within canvas throws + if (error) throw error + + const viewRef = React.useRef(null!) + const root = React.useRef>(null!) + + const [antialias, setAntialias] = React.useState(true) + + const onLayout = React.useCallback((e: LayoutChangeEvent) => { + const { width, height, x, y } = e.nativeEvent.layout + setSize({ width, height, top: y, left: x }) + }, []) + + // Called on context create or swap + // https://github.com/pmndrs/react-three-fiber/pull/2297 + const onContextCreate = React.useCallback((context: ExpoWebGLRenderingContext) => { + const canvasShim = { + width: context.drawingBufferWidth, + height: context.drawingBufferHeight, + style: {}, + addEventListener: (() => {}) as any, + removeEventListener: (() => {}) as any, + clientHeight: context.drawingBufferHeight, + getContext: ((_: any, { antialias = false }) => { + setAntialias(antialias) + return context + }) as any, + } as HTMLCanvasElement + + root.current = createRoot(canvasShim) + setCanvas(canvasShim) + }, []) + + if (root.current && width > 0 && height > 0) { + root.current.configure({ gl, - events = createTouchEvents, + events, shadows, linear, flat, @@ -39,132 +108,60 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef( raycaster, camera, scene, - onPointerMissed, - onCreated, - ...props - }, - forwardedRef, - ) => { - // Create a known catalogue of Threejs-native elements - // This will include the entire THREE namespace by default, users can extend - // their own elements by using the createRoot API instead - React.useMemo(() => extend(THREE as any), []) - - const Bridge = useBridge() - - const [{ width, height, top, left }, setSize] = React.useState({ width: 0, height: 0, top: 0, left: 0 }) - const [canvas, setCanvas] = React.useState(null) - const [bind, setBind] = React.useState() - React.useImperativeHandle(forwardedRef, () => viewRef.current) - - const handlePointerMissed = useMutableCallback(onPointerMissed) - const [block, setBlock] = React.useState(false) - const [error, setError] = React.useState(undefined) - - // Suspend this component if block is a promise (2nd run) - if (block) throw block - // Throw exception outwards if anything within canvas throws - if (error) throw error - - const viewRef = React.useRef(null!) - const root = React.useRef>(null!) - - const [antialias, setAntialias] = React.useState(true) - - const onLayout = React.useCallback((e: LayoutChangeEvent) => { - const { width, height, x, y } = e.nativeEvent.layout - setSize({ width, height, top: y, left: x }) - }, []) - - // Called on context create or swap - // https://github.com/pmndrs/react-three-fiber/pull/2297 - const onContextCreate = React.useCallback((context: ExpoWebGLRenderingContext) => { - const canvasShim = { - width: context.drawingBufferWidth, - height: context.drawingBufferHeight, - style: {}, - addEventListener: (() => {}) as any, - removeEventListener: (() => {}) as any, - clientHeight: context.drawingBufferHeight, - getContext: ((_: any, { antialias = false }) => { - setAntialias(antialias) - return context - }) as any, - } as HTMLCanvasElement - - root.current = createRoot(canvasShim) - setCanvas(canvasShim) - }, []) - - if (root.current && width > 0 && height > 0) { - root.current.configure({ - gl, - events, - shadows, - linear, - flat, - legacy, - orthographic, - frameloop, - performance, - raycaster, - camera, - scene, - // expo-gl can only render at native dpr/resolution - // https://github.com/expo/expo-three/issues/39 - dpr: PixelRatio.get(), - size: { width, height, top, left }, - // Pass mutable reference to onPointerMissed so it's free to update - onPointerMissed: (...args) => handlePointerMissed.current?.(...args), - // Overwrite onCreated to apply RN bindings - onCreated: (state: RootState) => { - // Bind events after creation - setBind(state.events.handlers) - - // Bind render to RN bridge - const context = state.gl.getContext() as ExpoWebGLRenderingContext - const renderFrame = state.gl.render.bind(state.gl) - state.gl.render = (scene: THREE.Scene, camera: THREE.Camera) => { - renderFrame(scene, camera) - context.endFrameEXP() - } - - return onCreated?.(state) - }, - }) - root.current.render( - - - }>{children} - - , - ) + // expo-gl can only render at native dpr/resolution + // https://github.com/expo/expo-three/issues/39 + dpr: PixelRatio.get(), + size: { width, height, top, left }, + // Pass mutable reference to onPointerMissed so it's free to update + onPointerMissed: (...args) => handlePointerMissed.current?.(...args), + // Overwrite onCreated to apply RN bindings + onCreated: (state: RootState) => { + // Bind events after creation + setBind(state.events.handlers) + + // Bind render to RN bridge + const context = state.gl.getContext() as ExpoWebGLRenderingContext + const renderFrame = state.gl.render.bind(state.gl) + state.gl.render = (scene: THREE.Scene, camera: THREE.Camera) => { + renderFrame(scene, camera) + context.endFrameEXP() + } + + return onCreated?.(state) + }, + }) + root.current.render( + + + }>{children} + + , + ) + } + + React.useEffect(() => { + if (canvas) { + return () => unmountComponentAtNode(canvas!) } + }, [canvas]) - React.useEffect(() => { - if (canvas) { - return () => unmountComponentAtNode(canvas!) - } - }, [canvas]) - - return ( - <_View {...props} ref={viewRef} onLayout={onLayout} style={{ flex: 1, ...style }} {...bind}> - {width > 0 && ( - - )} - - ) - }, -) + return ( + <_View {...props} ref={viewRef} onLayout={onLayout} style={{ flex: 1, ...style }} {...bind}> + {width > 0 && ( + + )} + + ) +} /** * A native canvas which accepts threejs elements as children. * @see https://docs.pmnd.rs/react-three-fiber/api/canvas */ -export const Canvas = React.forwardRef(function CanvasWrapper(props, ref) { +export function Canvas(props: CanvasProps) { return ( - + ) -}) +} diff --git a/packages/fiber/src/web/Canvas.tsx b/packages/fiber/src/web/Canvas.tsx index b88e0e4995..3f95983009 100644 --- a/packages/fiber/src/web/Canvas.tsx +++ b/packages/fiber/src/web/Canvas.tsx @@ -20,6 +20,7 @@ export interface CanvasProps extends Omit, 'size'>, React.HTMLAttributes { children: React.ReactNode + ref?: React.Ref /** Canvas fallback content, similar to img's alt prop */ fallback?: React.ReactNode /** @@ -35,33 +36,31 @@ export interface CanvasProps export interface Props extends CanvasProps {} -const CanvasImpl = /*#__PURE__*/ React.forwardRef(function Canvas( - { - children, - fallback, - resize, - style, - gl, - events = createPointerEvents, - eventSource, - eventPrefix, - shadows, - linear, - flat, - legacy, - orthographic, - frameloop, - dpr, - performance, - raycaster, - camera, - scene, - onPointerMissed, - onCreated, - ...props - }, - forwardedRef, -) { +function CanvasImpl({ + ref, + children, + fallback, + resize, + style, + gl, + events = createPointerEvents, + eventSource, + eventPrefix, + shadows, + linear, + flat, + legacy, + orthographic, + frameloop, + dpr, + performance, + raycaster, + camera, + scene, + onPointerMissed, + onCreated, + ...props +}: Props) { // Create a known catalogue of Threejs-native elements // This will include the entire THREE namespace by default, users can extend // their own elements by using the createRoot API instead @@ -72,7 +71,7 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef(func const [containerRef, containerRect] = useMeasure({ scroll: true, debounce: { scroll: 50, resize: 0 }, ...resize }) const canvasRef = React.useRef(null!) const divRef = React.useRef(null!) - React.useImperativeHandle(forwardedRef, () => canvasRef.current) + React.useImperativeHandle(ref, () => canvasRef.current) const handlePointerMissed = useMutableCallback(onPointerMissed) const [block, setBlock] = React.useState(false) @@ -164,16 +163,16 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef(func ) -}) +} /** * A DOM canvas which accepts threejs elements as children. * @see https://docs.pmnd.rs/react-three-fiber/api/canvas */ -export const Canvas = React.forwardRef(function CanvasWrapper(props, ref) { +export function Canvas(props: CanvasProps) { return ( - + ) -}) +}