diff --git a/package.json b/package.json index 42bdf7eaea..354a9703d9 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-native": "0.67.4", - "react-nil": "^1.2.0", "react-test-renderer": "^18.0.0", "regenerator-runtime": "^0.13.9", "three": "^0.139.0", diff --git a/packages/fiber/package.json b/packages/fiber/package.json index b9030ff890..acfdba55a9 100644 --- a/packages/fiber/package.json +++ b/packages/fiber/package.json @@ -44,6 +44,7 @@ "dependencies": { "@babel/runtime": "^7.17.8", "@types/react-reconciler": "^0.26.7", + "its-fine": "^1.0.0", "react-reconciler": "^0.27.0", "react-use-measure": "^2.1.1", "scheduler": "^0.21.0", diff --git a/packages/fiber/src/core/utils.ts b/packages/fiber/src/core/utils.ts index 996c84ec12..23cc8ae8be 100644 --- a/packages/fiber/src/core/utils.ts +++ b/packages/fiber/src/core/utils.ts @@ -4,7 +4,6 @@ import { UseBoundStore } from 'zustand' import { EventHandlers } from './events' import { AttachType, Instance, InstanceProps, LocalState } from './renderer' import { Dpr, RootState, Size } from './store' -import type { Fiber } from 'react-reconciler' export type Camera = THREE.OrthographicCamera | THREE.PerspectiveCamera export const isOrthographicCamera = (def: Camera): def is THREE.OrthographicCamera => @@ -31,88 +30,6 @@ export function useMutableCallback(fn: T) { return ref } -/** - * Traverses up or down a {@link Fiber}, return `true` to stop and select a node. - */ -function traverseFiber(fiber: Fiber, ascending: boolean, selector: (node: Fiber) => boolean | void): Fiber | undefined { - if (selector(fiber) === true) return fiber - - let child = ascending ? fiber.return : fiber.child - while (child) { - const match = traverseFiber(child, ascending, selector) - if (match) return match - - child = child.sibling - } -} - -// Active contexts -const contexts: React.Context[] = [] - -/** - * Represents a react-context bridge provider component. - */ -export type ContextBridge = React.FC<{ children?: React.ReactNode }> - -/** - * React Context currently cannot be shared across [React renderers](https://reactjs.org/docs/codebase-overview.html#renderers) but explicitly forwarded between providers (see [react#17275](https://github.com/facebook/react/issues/17275)). This hook returns a {@link ContextBridge} of live context providers to pierce Context across renderers. - * - * Pass {@link ContextBridge} as a component to a secondary renderer to enable context-sharing within its children. - */ -export function useContextBridge(fiber?: Fiber): ContextBridge { - if (fiber) { - traverseFiber(fiber, true, (node) => { - const context = node.type?._context - if (!context || contexts.includes(context)) return - - // In development, React will warn about using contexts between renderers because - // of the above issue. We'll hide the warning because this hook works as expected - // https://github.com/facebook/react/pull/12779 - Object.defineProperties(context, { - _currentRenderer: { - get() { - return null - }, - set() {}, - }, - _currentRenderer2: { - get() { - return null - }, - set() {}, - }, - }) - - contexts.push(context) - }) - } - - return contexts.reduce( - (Prev, context) => { - const value = ( - React as any - ).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current?.readContext(context) - return (props) => React.createElement(Prev, null, React.createElement(context.Provider, { ...props, value })) - }, - (props) => React.createElement(React.Fragment, props), - ) -} - -/** - * Exposes the current react-internal {@link Fiber}. - */ -export class FiberProvider extends React.Component<{ setFiber: React.Dispatch; children?: React.ReactNode }> { - private _reactInternals!: Fiber - - componentDidMount() { - this.props.setFiber(this._reactInternals) - } - - render() { - return this.props.children - } -} - export type SetBlock = false | Promise | null export type UnblockProps = { set: React.Dispatch>; children: React.ReactNode } diff --git a/packages/fiber/src/native/Canvas.tsx b/packages/fiber/src/native/Canvas.tsx index 90865b98a9..a3d90def9b 100644 --- a/packages/fiber/src/native/Canvas.tsx +++ b/packages/fiber/src/native/Canvas.tsx @@ -2,7 +2,8 @@ import * as React from 'react' import * as THREE from 'three' import { View, ViewProps, ViewStyle, LayoutChangeEvent, StyleSheet, PixelRatio } from 'react-native' import { ExpoWebGLRenderingContext, GLView } from 'expo-gl' -import { SetBlock, Block, ErrorBoundary, useMutableCallback, useContextBridge, FiberProvider } from '../core/utils' +import { useContextBridge, FiberProvider } from 'its-fine' +import { SetBlock, Block, ErrorBoundary, useMutableCallback } from '../core/utils' import { extend, createRoot, unmountComponentAtNode, RenderProps, ReconcilerRoot } from '../core' import { createTouchEvents } from './events' import { RootState, Size } from '../core/store' @@ -17,7 +18,7 @@ export interface Props extends Omit, 'size' | 'dp * A native canvas which accepts threejs elements as children. * @see https://docs.pmnd.rs/react-three-fiber/api/canvas */ -export const Canvas = /*#__PURE__*/ React.forwardRef( +const CanvasImpl = /*#__PURE__*/ React.forwardRef( ( { children, @@ -44,8 +45,7 @@ export const Canvas = /*#__PURE__*/ React.forwardRef( // their own elements by using the createRoot API instead React.useMemo(() => extend(THREE), []) - const [fiber, setFiber] = React.useState(null) - const Bridge = useContextBridge(fiber) + const Bridge = useContextBridge() const [{ width, height, top, left }, setSize] = React.useState({ width: 0, height: 0, top: 0, left: 0 }) const [canvas, setCanvas] = React.useState(null) @@ -141,11 +141,21 @@ export const Canvas = /*#__PURE__*/ React.forwardRef( }, [canvas]) return ( - - - {width > 0 && } - - + + {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) { + return ( + + + + ) +}) diff --git a/packages/fiber/src/web/Canvas.tsx b/packages/fiber/src/web/Canvas.tsx index d9bb3a6600..1afaccb8be 100644 --- a/packages/fiber/src/web/Canvas.tsx +++ b/packages/fiber/src/web/Canvas.tsx @@ -2,16 +2,8 @@ import * as React from 'react' import * as THREE from 'three' import useMeasure from 'react-use-measure' import type { Options as ResizeOptions } from 'react-use-measure' -import { - isRef, - SetBlock, - Block, - ErrorBoundary, - useMutableCallback, - useIsomorphicLayoutEffect, - useContextBridge, - FiberProvider, -} from '../core/utils' +import { useContextBridge, FiberProvider } from 'its-fine' +import { isRef, SetBlock, Block, ErrorBoundary, useMutableCallback, useIsomorphicLayoutEffect } from '../core/utils' import { ReconcilerRoot, extend, createRoot, unmountComponentAtNode, RenderProps } from '../core' import { createPointerEvents } from './events' import { DomEvent } from '../core/events' @@ -31,11 +23,7 @@ export interface Props extends Omit, 'size'>, Rea eventPrefix?: 'offset' | 'client' | 'page' | 'layer' | 'screen' } -/** - * A DOM canvas which accepts threejs elements as children. - * @see https://docs.pmnd.rs/react-three-fiber/api/canvas - */ -export const Canvas = /*#__PURE__*/ React.forwardRef(function Canvas( +const CanvasImpl = /*#__PURE__*/ React.forwardRef(function Canvas( { children, fallback, @@ -66,8 +54,7 @@ export const Canvas = /*#__PURE__*/ React.forwardRef(f // their own elements by using the createRoot API instead React.useMemo(() => extend(THREE), []) - const [fiber, setFiber] = React.useState(null) - const Bridge = useContextBridge(fiber) + const Bridge = useContextBridge() const [containerRef, containerRect] = useMeasure({ scroll: true, debounce: { scroll: 50, resize: 0 }, ...resize }) const canvasRef = React.useRef(null!) @@ -144,17 +131,27 @@ export const Canvas = /*#__PURE__*/ React.forwardRef(f const pointerEvents = eventSource ? 'none' : 'auto' return ( - -
-
- - {fallback} - -
+
+
+ + {fallback} +
+
+ ) +}) + +/** + * 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) { + return ( + + ) }) diff --git a/packages/fiber/tests/core/utils.test.tsx b/packages/fiber/tests/core/utils.test.ts similarity index 63% rename from packages/fiber/tests/core/utils.test.tsx rename to packages/fiber/tests/core/utils.test.ts index c6fba16548..9600d68232 100644 --- a/packages/fiber/tests/core/utils.test.tsx +++ b/packages/fiber/tests/core/utils.test.ts @@ -1,7 +1,4 @@ -import * as React from 'react' -import { act, render } from 'react-nil' -import { create } from 'react-test-renderer' -import { is, useContextBridge, FiberProvider } from '../../src/core/utils' +import { is } from '../../src/core/utils' describe('is', () => { const myFunc = () => null @@ -100,71 +97,3 @@ describe('is', () => { expect(is.equ([1, 2], [1, 2, 3], { strict: false })).toBe(true) }) }) - -describe('useContextBridge', () => { - it('forwards live context between renderers', async () => { - const Context1 = React.createContext(null!) - const Context2 = React.createContext(null!) - - const values: string[] = [] - - function Test() { - values.push(React.useContext(Context1), React.useContext(Context2)) - - return null - } - - const Canvas = React.memo(() => { - const [fiber, setFiber] = React.useState(null) - const Bridge = useContextBridge(fiber) - render( - - - , - ) - - return ( - - - - ) - }) - - function Providers(props: { values: [value1: string, value2?: string]; children: React.ReactNode }) { - const [value1, value2] = props.values - return ( - - - {value2 ? {props.children} : props.children} - - - ) - } - - await act(async () => - create( - - - , - ), - ) - - await act(async () => - create( - - - , - ), - ) - - await act(async () => - create( - - - , - ), - ) - - expect(values).toStrictEqual(['value1', 'value2', 'value1__new', 'value2__new', 'value1__new2', null]) - }) -}) diff --git a/yarn.lock b/yarn.lock index 0ac83d495e..6729026144 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2207,6 +2207,13 @@ dependencies: "@types/react" "*" +"@types/react-reconciler@^0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.0.tgz#513acbed173140e958c909041ca14eb40412077f" + integrity sha512-5cjk9ottZAj7eaTsqzPUIlrVbh3hBAO2YaEL1rkjHKB3xNAId7oU8GhzvAX+gfmlfoxTwJnBjPxEHyxkEA1Ffg== + dependencies: + "@types/react" "*" + "@types/react-test-renderer@^17.0.1": version "17.0.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.2.tgz#5f800a39b12ac8d2a2149e7e1885215bcf4edbbf" @@ -5359,6 +5366,13 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +its-fine@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/its-fine/-/its-fine-1.0.0.tgz#e1c17f4420a433c9e96d264330e96a82c6edc33b" + integrity sha512-EEVcyr+sR21lxLZg3U84HpY3Mb9aKmGYqxPsIbf/Ea4fO4qpvE/7lX5qtrkuCnljJ9RLMaSaeMq35PeLa+oM0w== + dependencies: + "@types/react-reconciler" "^0.28.0" + jest-changed-files@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" @@ -7586,14 +7600,6 @@ react-native@0.67.4: whatwg-fetch "^3.0.0" ws "^6.1.4" -react-nil@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/react-nil/-/react-nil-1.2.0.tgz#0f2e110b50cf12bdf1eebf43bfdb4b348a271c11" - integrity sha512-54yJ8+vFyJlZHiFBLny0RKQ0/FSG32y8pZt0oV7duxaW3lEM9pc6D2Hhnvu0TToJKPCAJ/A5XmjY8nDETxrrGg== - dependencies: - "@types/react-reconciler" "^0.26.7" - react-reconciler "^0.27.0" - react-reconciler@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b"