diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js index e0e883c4b2..8c50b26ecc 100644 --- a/packages/react-art/src/ReactARTHostConfig.js +++ b/packages/react-art/src/ReactARTHostConfig.js @@ -306,6 +306,7 @@ export function getPublicInstance(instance) { export function prepareForCommit() { // Noop + return null; } export function prepareUpdate(domElement, type, oldProps, newProps) { @@ -507,3 +508,11 @@ export function unmountEventListener(listener: any) { export function validateEventListenerTarget(target: any, listener: any) { throw new Error('Not yet implemented.'); } + +export function beforeActiveInstanceBlur() { + // noop +} + +export function afterActiveInstanceBlur() { + // noop +} diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index e2e9211234..70f23dba42 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -91,10 +91,6 @@ import { import {getListenerMapForElement} from '../events/DOMEventListenerMap'; import {TOP_BEFORE_BLUR, TOP_AFTER_BLUR} from '../events/DOMTopLevelEventTypes'; -// TODO: This is an exposed internal, we should move this around -// so this isn't the case. -import {isFiberInsideHiddenOrRemovedTree} from 'react-reconciler/src/ReactFiberTreeReflection'; - export type ReactListenerEvent = ReactDOMListenerEvent; export type ReactListenerMap = ReactDOMListenerMap; export type ReactListener = ReactDOMListener; @@ -159,7 +155,6 @@ export opaque type OpaqueIDType = }; type SelectionInformation = {| - activeElementDetached: null | HTMLElement, focusedElem: null | HTMLElement, selectionRange: mixed, |}; @@ -247,32 +242,40 @@ export function getPublicInstance(instance: Instance): * { return instance; } -export function prepareForCommit(containerInfo: Container): void { +export function prepareForCommit(containerInfo: Container): Object | null { eventsEnabled = ReactBrowserEventEmitterIsEnabled(); selectionInformation = getSelectionInformation(); + let activeInstance = null; if (enableDeprecatedFlareAPI || enableUseEventAPI) { const focusedElem = selectionInformation.focusedElem; if (focusedElem !== null) { - const instance = getClosestInstanceFromNode(focusedElem); - if (instance !== null && isFiberInsideHiddenOrRemovedTree(instance)) { - dispatchBeforeDetachedBlur(focusedElem); - } + activeInstance = getClosestInstanceFromNode(focusedElem); } } ReactBrowserEventEmitterSetEnabled(false); + return activeInstance; +} + +export function beforeActiveInstanceBlur(): void { + if (enableDeprecatedFlareAPI || enableUseEventAPI) { + ReactBrowserEventEmitterSetEnabled(true); + dispatchBeforeDetachedBlur((selectionInformation: any).focusedElem); + ReactBrowserEventEmitterSetEnabled(false); + } +} + +export function afterActiveInstanceBlur(): void { + if (enableDeprecatedFlareAPI || enableUseEventAPI) { + ReactBrowserEventEmitterSetEnabled(true); + dispatchAfterDetachedBlur((selectionInformation: any).focusedElem); + ReactBrowserEventEmitterSetEnabled(false); + } } export function resetAfterCommit(containerInfo: Container): void { restoreSelection(selectionInformation); ReactBrowserEventEmitterSetEnabled(eventsEnabled); eventsEnabled = null; - if (enableDeprecatedFlareAPI || enableUseEventAPI) { - const activeElementDetached = (selectionInformation: any) - .activeElementDetached; - if (activeElementDetached !== null) { - dispatchAfterDetachedBlur(activeElementDetached); - } - } selectionInformation = null; } @@ -525,8 +528,6 @@ function createEvent(type: TopLevelType): Event { } function dispatchBeforeDetachedBlur(target: HTMLElement): void { - ((selectionInformation: any): SelectionInformation).activeElementDetached = target; - if (enableDeprecatedFlareAPI || enableUseEventAPI) { const event = createEvent(TOP_BEFORE_BLUR); // Dispatch "beforeblur" directly on the target, diff --git a/packages/react-dom/src/client/ReactInputSelection.js b/packages/react-dom/src/client/ReactInputSelection.js index 7d05a3d733..1465872a0e 100644 --- a/packages/react-dom/src/client/ReactInputSelection.js +++ b/packages/react-dom/src/client/ReactInputSelection.js @@ -100,8 +100,6 @@ export function hasSelectionCapabilities(elem) { export function getSelectionInformation() { const focusedElem = getActiveElementDeep(); return { - // Used by Flare - activeElementDetached: null, focusedElem: focusedElem, selectionRange: hasSelectionCapabilities(focusedElem) ? getSelection(focusedElem) diff --git a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js index be5052fdbd..aedf567f58 100644 --- a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js @@ -16,7 +16,8 @@ let ReactFeatureFlags; let ReactDOM; let FocusWithinResponder; let useFocusWithin; -let Scheduler; +let ReactTestRenderer; +let act; const initializeModules = hasPointerEvents => { setPointerEvent(hasPointerEvents); @@ -26,7 +27,8 @@ const initializeModules = hasPointerEvents => { ReactFeatureFlags.enableScopeAPI = true; React = require('react'); ReactDOM = require('react-dom'); - Scheduler = require('scheduler'); + ReactTestRenderer = require('react-test-renderer'); + act = ReactTestRenderer.act; // TODO: This import throws outside of experimental mode. Figure out better // strategy for gated imports. @@ -43,17 +45,22 @@ const table = [[forcePointerEvents], [!forcePointerEvents]]; describe.each(table)('FocusWithin responder', hasPointerEvents => { let container; + let container2; beforeEach(() => { initializeModules(); container = document.createElement('div'); document.body.appendChild(container); + container2 = document.createElement('div'); + document.body.appendChild(container2); }); afterEach(() => { ReactDOM.render(null, container); document.body.removeChild(container); + document.body.removeChild(container2); container = null; + container2 = null; }); describe('disabled', () => { @@ -366,6 +373,40 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { ); }); + // @gate experimental + it('is called after many elements are unmounted', () => { + const buttonRef = React.createRef(); + const inputRef = React.createRef(); + + const Component = ({show}) => { + const listener = useFocusWithin({ + onBeforeBlurWithin, + onAfterBlurWithin, + }); + return ( +
+ {show && } + {show && } + {show && } + {show && } + {!show && } + {show && } + + +
+ ); + }; + + ReactDOM.render(, container); + + inputRef.current.focus(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + ReactDOM.render(, container); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); + }); + // @gate experimental it('is called after a nested focused element is unmounted (with scope query)', () => { const TestScope = React.unstable_createScope(); @@ -430,12 +471,10 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { ); }; - const container2 = document.createElement('div'); - document.body.appendChild(container2); - const root = ReactDOM.createRoot(container2); - root.render(); - Scheduler.unstable_flushAll(); + act(() => { + root.render(); + }); jest.runAllTimers(); expect(container2.innerHTML).toBe('
'); @@ -447,8 +486,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); suspend = true; - root.render(); - Scheduler.unstable_flushAll(); + act(() => { + root.render(); + }); jest.runAllTimers(); expect(container2.innerHTML).toBe( '
Loading...
', @@ -456,8 +496,74 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); resolve(); + }); - document.body.removeChild(container2); + // @gate experimental + it('is called after a focused suspended element is hidden then shown', () => { + const Suspense = React.Suspense; + let suspend = false; + let resolve; + const promise = new Promise(resolvePromise => (resolve = resolvePromise)); + const buttonRef = React.createRef(); + + function Child() { + if (suspend) { + throw promise; + } else { + return ; + } + } + + const Component = ({show}) => { + const listener = useFocusWithin({ + onBeforeBlurWithin, + onAfterBlurWithin, + }); + + return ( +
+ Loading...}> + + +
+ ); + }; + + const root = ReactDOM.createRoot(container2); + + act(() => { + root.render(); + }); + jest.runAllTimers(); + + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + + suspend = true; + act(() => { + root.render(); + }); + jest.runAllTimers(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + + act(() => { + root.render(); + }); + jest.runAllTimers(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + + buttonRef.current.focus(); + suspend = false; + act(() => { + root.render(); + }); + jest.runAllTimers(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); + + resolve(); }); }); diff --git a/packages/react-interactions/events/src/dom/use-event/__tests__/useFocusWithin-test.internal.js b/packages/react-interactions/events/src/dom/use-event/__tests__/useFocusWithin-test.internal.js index 416830c455..70d3e8791e 100644 --- a/packages/react-interactions/events/src/dom/use-event/__tests__/useFocusWithin-test.internal.js +++ b/packages/react-interactions/events/src/dom/use-event/__tests__/useFocusWithin-test.internal.js @@ -42,17 +42,22 @@ const table = [[forcePointerEvents], [!forcePointerEvents]]; describe.each(table)(`useFocus`, hasPointerEvents => { let container; + let container2; beforeEach(() => { initializeModules(hasPointerEvents); container = document.createElement('div'); document.body.appendChild(container); + container2 = document.createElement('div'); + document.body.appendChild(container2); }); afterEach(() => { ReactDOM.render(null, container); document.body.removeChild(container); + document.body.removeChild(container2); container = null; + container2 = null; }); describe('disabled', () => { @@ -367,6 +372,40 @@ describe.each(table)(`useFocus`, hasPointerEvents => { ); }); + // @gate experimental + it('is called after many elements are unmounted', () => { + const buttonRef = React.createRef(); + const inputRef = React.createRef(); + + const Component = ({show}) => { + useFocusWithin(ref, { + onBeforeBlurWithin, + onAfterBlurWithin, + }); + return ( +
+ {show && } + {show && } + {show && } + {show && } + {!show && } + {show && } + + +
+ ); + }; + + ReactDOM.render(, container); + + inputRef.current.focus(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + ReactDOM.render(, container); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); + }); + // @gate experimental it('is called after a nested focused element is unmounted (with scope query)', () => { const TestScope = React.unstable_createScope(); @@ -431,9 +470,6 @@ describe.each(table)(`useFocus`, hasPointerEvents => { ); }; - const container2 = document.createElement('div'); - document.body.appendChild(container2); - const root = ReactDOM.createRoot(container2); act(() => { @@ -460,8 +496,74 @@ describe.each(table)(`useFocus`, hasPointerEvents => { expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); resolve(); + }); - document.body.removeChild(container2); + // @gate experimental + it('is called after a focused suspended element is hidden then shown', () => { + const Suspense = React.Suspense; + let suspend = false; + let resolve; + const promise = new Promise(resolvePromise => (resolve = resolvePromise)); + const buttonRef = React.createRef(); + + function Child() { + if (suspend) { + throw promise; + } else { + return ; + } + } + + const Component = ({show}) => { + useFocusWithin(ref, { + onBeforeBlurWithin, + onAfterBlurWithin, + }); + + return ( +
+ Loading...}> + + +
+ ); + }; + + const root = ReactDOM.createRoot(container2); + + act(() => { + root.render(); + }); + jest.runAllTimers(); + + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + + suspend = true; + act(() => { + root.render(); + }); + jest.runAllTimers(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + + act(() => { + root.render(); + }); + jest.runAllTimers(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + + buttonRef.current.focus(); + suspend = false; + act(() => { + root.render(); + }); + jest.runAllTimers(); + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); + expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); + + resolve(); }); }); }); diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index 91aa983981..5b1d0edb8d 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -304,8 +304,9 @@ export function getPublicInstance(instance: Instance): * { return instance.canonical; } -export function prepareForCommit(containerInfo: Container): void { +export function prepareForCommit(containerInfo: Container): null | Object { // Noop + return null; } export function prepareUpdate( @@ -524,3 +525,11 @@ export function unmountEventListener(listener: any) { export function validateEventListenerTarget(target: any, listener: any) { throw new Error('Not yet implemented.'); } + +export function beforeActiveInstanceBlur() { + // noop +} + +export function afterActiveInstanceBlur() { + // noop +} diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js index a6ed4de72c..726c743247 100644 --- a/packages/react-native-renderer/src/ReactNativeHostConfig.js +++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js @@ -223,8 +223,9 @@ export function getPublicInstance(instance: Instance): * { return instance; } -export function prepareForCommit(containerInfo: Container): void { +export function prepareForCommit(containerInfo: Container): null | Object { // Noop + return null; } export function prepareUpdate( @@ -573,3 +574,11 @@ export function unmountEventListener(listener: any) { export function validateEventListenerTarget(target: any, listener: any) { throw new Error('Not yet implemented.'); } + +export function beforeActiveInstanceBlur() { + // noop +} + +export function afterActiveInstanceBlur() { + // noop +} diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index e99c250b3b..7d26eb47bb 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -363,7 +363,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { cancelTimeout: clearTimeout, noTimeout: -1, - prepareForCommit(): void {}, + prepareForCommit(): null | Object { + return null; + }, resetAfterCommit(): void {}, @@ -439,6 +441,14 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { beforeRemoveInstance(instance: any): void { // NO-OP }, + + beforeActiveInstanceBlur() { + // NO-OP + }, + + afterActiveInstanceBlur() { + // NO-OP + }, }; const hostConfig = useMutation diff --git a/packages/react-reconciler/src/ReactFiberTreeReflection.js b/packages/react-reconciler/src/ReactFiberTreeReflection.js index a6a4b454e8..1a8be6e497 100644 --- a/packages/react-reconciler/src/ReactFiberTreeReflection.js +++ b/packages/react-reconciler/src/ReactFiberTreeReflection.js @@ -342,20 +342,45 @@ export function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean { ); } -// This is only safe to call in the commit phase when the return tree is consistent. -// It should not be used anywhere else. See PR #18609 for details. -export function isFiberInsideHiddenOrRemovedTree(fiber: Fiber): boolean { - let node = fiber; - let lastChild = null; +function doesFiberContain(parentFiber: Fiber, childFiber: Fiber): boolean { + let node = childFiber; + const parentFiberAlternate = parentFiber.alternate; while (node !== null) { - if ( - node.effectTag & Deletion || - (isFiberSuspenseAndTimedOut(node) && node.child === lastChild) - ) { + if (node === parentFiber || node === parentFiberAlternate) { return true; } - lastChild = node; node = node.return; } return false; } + +function isFiberTimedOutSuspenseThatContainsTargetFiber( + fiber: Fiber, + targetFiber: Fiber, +): boolean { + const child = fiber.child; + return ( + isFiberSuspenseAndTimedOut(fiber) && + child !== null && + doesFiberContain(child, targetFiber) + ); +} + +function isFiberDeletedAndContainsTargetFiber( + fiber: Fiber, + targetFiber: Fiber, +): boolean { + return ( + (fiber.effectTag & Deletion) !== 0 && doesFiberContain(fiber, targetFiber) + ); +} + +export function isFiberHiddenOrDeletedAndContains( + parentFiber: Fiber, + childFiber: Fiber, +): boolean { + return ( + isFiberDeletedAndContainsTargetFiber(parentFiber, childFiber) || + isFiberTimedOutSuspenseThatContainsTargetFiber(parentFiber, childFiber) + ); +} diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 376a7c9670..0428797388 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -72,6 +72,8 @@ import { cancelTimeout, noTimeout, warnsIfNotActing, + beforeActiveInstanceBlur, + afterActiveInstanceBlur, } from './ReactFiberHostConfig'; import { @@ -193,6 +195,7 @@ import {onCommitRoot} from './ReactFiberDevToolsHook.new'; // Used by `act` import enqueueTask from 'shared/enqueueTask'; +import {isFiberHiddenOrDeletedAndContains} from './ReactFiberTreeReflection'; const ceil = Math.ceil; @@ -298,6 +301,9 @@ let currentEventTime: ExpirationTime = NoWork; // We warn about state updates for unmounted components differently in this case. let isFlushingPassiveEffects = false; +let focusedInstanceHandle: null | Fiber = null; +let shouldFireAfterActiveInstanceBlur: boolean = false; + export function getWorkInProgressRoot(): FiberRoot | null { return workInProgressRoot; } @@ -1902,7 +1908,9 @@ function commitRootImpl(root, renderPriorityLevel) { // The first phase a "before mutation" phase. We use this phase to read the // state of the host tree right before we mutate it. This is where // getSnapshotBeforeUpdate is called. - prepareForCommit(root.containerInfo); + focusedInstanceHandle = prepareForCommit(root.containerInfo); + shouldFireAfterActiveInstanceBlur = false; + nextEffect = firstEffect; do { if (__DEV__) { @@ -1924,6 +1932,9 @@ function commitRootImpl(root, renderPriorityLevel) { } } while (nextEffect !== null); + // We no longer need to track the active instance fiber + focusedInstanceHandle = null; + if (enableProfilerTimer) { // Mark the current commit time to be shared by all Profilers in this // batch. This enables them to be grouped later. @@ -1957,6 +1968,10 @@ function commitRootImpl(root, renderPriorityLevel) { } } } while (nextEffect !== null); + + if (shouldFireAfterActiveInstanceBlur) { + afterActiveInstanceBlur(); + } resetAfterCommit(root.containerInfo); // The work-in-progress tree is now the current tree. This must come after @@ -2124,6 +2139,14 @@ function commitRootImpl(root, renderPriorityLevel) { function commitBeforeMutationEffects() { while (nextEffect !== null) { + if ( + !shouldFireAfterActiveInstanceBlur && + focusedInstanceHandle !== null && + isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle) + ) { + shouldFireAfterActiveInstanceBlur = true; + beforeActiveInstanceBlur(); + } const effectTag = nextEffect.effectTag; if ((effectTag & Snapshot) !== NoEffect) { setCurrentDebugFiberInDEV(nextEffect); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 713bcc23f8..5dd70549f2 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -72,6 +72,8 @@ import { cancelTimeout, noTimeout, warnsIfNotActing, + beforeActiveInstanceBlur, + afterActiveInstanceBlur, } from './ReactFiberHostConfig'; import { @@ -191,6 +193,7 @@ import {onCommitRoot} from './ReactFiberDevToolsHook.old'; // Used by `act` import enqueueTask from 'shared/enqueueTask'; +import {isFiberHiddenOrDeletedAndContains} from './ReactFiberTreeReflection'; const ceil = Math.ceil; @@ -296,6 +299,9 @@ let currentEventTime: ExpirationTime = NoWork; // We warn about state updates for unmounted components differently in this case. let isFlushingPassiveEffects = false; +let focusedInstanceHandle: null | Fiber = null; +let shouldFireAfterActiveInstanceBlur: boolean = false; + export function getWorkInProgressRoot(): FiberRoot | null { return workInProgressRoot; } @@ -1921,7 +1927,9 @@ function commitRootImpl(root, renderPriorityLevel) { // The first phase a "before mutation" phase. We use this phase to read the // state of the host tree right before we mutate it. This is where // getSnapshotBeforeUpdate is called. - prepareForCommit(root.containerInfo); + focusedInstanceHandle = prepareForCommit(root.containerInfo); + shouldFireAfterActiveInstanceBlur = false; + nextEffect = firstEffect; do { if (__DEV__) { @@ -1943,6 +1951,9 @@ function commitRootImpl(root, renderPriorityLevel) { } } while (nextEffect !== null); + // We no longer need to track the active instance fiber + focusedInstanceHandle = null; + if (enableProfilerTimer) { // Mark the current commit time to be shared by all Profilers in this // batch. This enables them to be grouped later. @@ -1976,6 +1987,10 @@ function commitRootImpl(root, renderPriorityLevel) { } } } while (nextEffect !== null); + + if (shouldFireAfterActiveInstanceBlur) { + afterActiveInstanceBlur(); + } resetAfterCommit(root.containerInfo); // The work-in-progress tree is now the current tree. This must come after @@ -2143,6 +2158,14 @@ function commitRootImpl(root, renderPriorityLevel) { function commitBeforeMutationEffects() { while (nextEffect !== null) { + if ( + !shouldFireAfterActiveInstanceBlur && + focusedInstanceHandle !== null && + isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle) + ) { + shouldFireAfterActiveInstanceBlur = true; + beforeActiveInstanceBlur(); + } const effectTag = nextEffect.effectTag; if ((effectTag & Snapshot) !== NoEffect) { setCurrentDebugFiberInDEV(nextEffect); diff --git a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js index 9fc4f552da..1237a89c74 100644 --- a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js @@ -25,7 +25,9 @@ describe('ReactFiberHostContext', () => { it('works with null host context', () => { let creates = 0; const Renderer = ReactFiberReconciler({ - prepareForCommit: function() {}, + prepareForCommit: function() { + return null; + }, resetAfterCommit: function() {}, getRootHostContext: function() { return null; @@ -76,6 +78,7 @@ describe('ReactFiberHostContext', () => { const Renderer = ReactFiberReconciler({ prepareForCommit: function(hostContext) { expect(hostContext).toBe(rootContext); + return null; }, resetAfterCommit: function(hostContext) { expect(hostContext).toBe(rootContext); diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js index 7af28eac24..eb3447f3f9 100644 --- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js @@ -89,6 +89,8 @@ export const makeOpaqueHydratingObject = export const makeClientId = $$$hostConfig.makeClientId; export const makeClientIdInDEV = $$$hostConfig.makeClientIdInDEV; export const makeServerId = $$$hostConfig.makeServerId; +export const beforeActiveInstanceBlur = $$$hostConfig.beforeActiveInstanceBlur; +export const afterActiveInstanceBlur = $$$hostConfig.afterActiveInstanceBlur; // ------------------- // Mutation diff --git a/packages/react-test-renderer/src/ReactTestHostConfig.js b/packages/react-test-renderer/src/ReactTestHostConfig.js index c3eda48d2a..b9c2c4adf4 100644 --- a/packages/react-test-renderer/src/ReactTestHostConfig.js +++ b/packages/react-test-renderer/src/ReactTestHostConfig.js @@ -143,8 +143,9 @@ export function getChildHostContext( return NO_CONTEXT; } -export function prepareForCommit(containerInfo: Container): void { +export function prepareForCommit(containerInfo: Container): null | Object { // noop + return null; } export function resetAfterCommit(containerInfo: Container): void { @@ -445,3 +446,11 @@ export function unmountEventListener(listener: any) { export function validateEventListenerTarget(target: any, listener: any) { throw new Error('Not yet implemented.'); } + +export function beforeActiveInstanceBlur() { + // noop +} + +export function afterActiveInstanceBlur() { + // noop +}