mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add detach to Offscreen component (#25265)
This commit is contained in:
committed by
Rick Hanlon
parent
82b1e2c9f6
commit
073c2fa2eb
@@ -73,7 +73,6 @@ import {
|
||||
} from './ReactWorkTags';
|
||||
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
|
||||
import {
|
||||
resolveClassForHotReloading,
|
||||
@@ -109,6 +108,7 @@ import {
|
||||
REACT_TRACING_MARKER_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.new';
|
||||
import {detachOffscreenInstance} from './ReactFiberCommitWork.new';
|
||||
|
||||
export type {Fiber};
|
||||
|
||||
@@ -755,6 +755,8 @@ export function createFiberFromOffscreen(
|
||||
_pendingMarkers: null,
|
||||
_retryCache: null,
|
||||
_transitions: null,
|
||||
_current: null,
|
||||
detach: () => detachOffscreenInstance(primaryChildInstance),
|
||||
};
|
||||
fiber.stateNode = primaryChildInstance;
|
||||
return fiber;
|
||||
@@ -776,6 +778,8 @@ export function createFiberFromLegacyHidden(
|
||||
_pendingMarkers: null,
|
||||
_transitions: null,
|
||||
_retryCache: null,
|
||||
_current: null,
|
||||
detach: () => detachOffscreenInstance(instance),
|
||||
};
|
||||
fiber.stateNode = instance;
|
||||
return fiber;
|
||||
|
||||
@@ -73,7 +73,6 @@ import {
|
||||
} from './ReactWorkTags';
|
||||
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
|
||||
import {
|
||||
resolveClassForHotReloading,
|
||||
@@ -109,6 +108,7 @@ import {
|
||||
REACT_TRACING_MARKER_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.old';
|
||||
import {detachOffscreenInstance} from './ReactFiberCommitWork.old';
|
||||
|
||||
export type {Fiber};
|
||||
|
||||
@@ -755,6 +755,8 @@ export function createFiberFromOffscreen(
|
||||
_pendingMarkers: null,
|
||||
_retryCache: null,
|
||||
_transitions: null,
|
||||
_current: null,
|
||||
detach: () => detachOffscreenInstance(primaryChildInstance),
|
||||
};
|
||||
fiber.stateNode = primaryChildInstance;
|
||||
return fiber;
|
||||
@@ -776,6 +778,8 @@ export function createFiberFromLegacyHidden(
|
||||
_pendingMarkers: null,
|
||||
_transitions: null,
|
||||
_retryCache: null,
|
||||
_current: null,
|
||||
detach: () => detachOffscreenInstance(instance),
|
||||
};
|
||||
fiber.stateNode = instance;
|
||||
return fiber;
|
||||
|
||||
@@ -29,6 +29,7 @@ import type {
|
||||
OffscreenQueue,
|
||||
OffscreenInstance,
|
||||
} from './ReactFiberOffscreenComponent';
|
||||
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
|
||||
import type {
|
||||
Cache,
|
||||
CacheComponentState,
|
||||
@@ -37,7 +38,6 @@ import type {
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
|
||||
import type {RootState} from './ReactFiberRoot.new';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
import checkPropTypes from 'shared/checkPropTypes';
|
||||
import {
|
||||
markComponentRenderStarted,
|
||||
@@ -688,7 +688,10 @@ function updateOffscreenComponent(
|
||||
|
||||
if (
|
||||
nextProps.mode === 'hidden' ||
|
||||
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
|
||||
(enableLegacyHidden &&
|
||||
nextProps.mode === 'unstable-defer-without-hiding') ||
|
||||
// TODO: remove read from stateNode.
|
||||
workInProgress.stateNode._visibility & OffscreenDetached
|
||||
) {
|
||||
// Rendering a hidden tree.
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import type {
|
||||
OffscreenQueue,
|
||||
OffscreenInstance,
|
||||
} from './ReactFiberOffscreenComponent';
|
||||
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
|
||||
import type {
|
||||
Cache,
|
||||
CacheComponentState,
|
||||
@@ -37,7 +38,6 @@ import type {
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
|
||||
import type {RootState} from './ReactFiberRoot.old';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
|
||||
|
||||
import checkPropTypes from 'shared/checkPropTypes';
|
||||
import {
|
||||
markComponentRenderStarted,
|
||||
@@ -688,7 +688,10 @@ function updateOffscreenComponent(
|
||||
|
||||
if (
|
||||
nextProps.mode === 'hidden' ||
|
||||
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
|
||||
(enableLegacyHidden &&
|
||||
nextProps.mode === 'unstable-defer-without-hiding') ||
|
||||
// TODO: remove read from stateNode.
|
||||
workInProgress.stateNode._visibility & OffscreenDetached
|
||||
) {
|
||||
// Rendering a hidden tree.
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
|
||||
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
|
||||
import type {Wakeable} from 'shared/ReactTypes';
|
||||
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
|
||||
import type {
|
||||
OffscreenState,
|
||||
OffscreenInstance,
|
||||
@@ -156,6 +157,7 @@ import {
|
||||
clearSingleton,
|
||||
acquireSingletonInstance,
|
||||
releaseSingletonInstance,
|
||||
scheduleMicrotask,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {
|
||||
captureCommitPhaseError,
|
||||
@@ -172,6 +174,7 @@ import {
|
||||
setIsRunningInsertionEffect,
|
||||
getExecutionContext,
|
||||
CommitContext,
|
||||
RenderContext,
|
||||
NoContext,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {
|
||||
@@ -200,6 +203,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
|
||||
import {clearTransitionsForLanes} from './ReactFiberLane.new';
|
||||
import {
|
||||
OffscreenVisible,
|
||||
OffscreenDetached,
|
||||
OffscreenPassiveEffectsConnected,
|
||||
} from './ReactFiberOffscreenComponent';
|
||||
import {
|
||||
@@ -2416,6 +2420,28 @@ function getRetryCache(finishedWork) {
|
||||
}
|
||||
}
|
||||
|
||||
export function detachOffscreenInstance(instance: OffscreenInstance): void {
|
||||
const currentOffscreenFiber = instance._current;
|
||||
if (currentOffscreenFiber === null) {
|
||||
throw new Error(
|
||||
'Calling Offscreen.detach before instance handle has been set.',
|
||||
);
|
||||
}
|
||||
|
||||
const executionContext = getExecutionContext();
|
||||
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
|
||||
scheduleMicrotask(() => {
|
||||
instance._visibility |= OffscreenDetached;
|
||||
disappearLayoutEffects(currentOffscreenFiber);
|
||||
disconnectPassiveEffect(currentOffscreenFiber);
|
||||
});
|
||||
} else {
|
||||
instance._visibility |= OffscreenDetached;
|
||||
disappearLayoutEffects(currentOffscreenFiber);
|
||||
disconnectPassiveEffect(currentOffscreenFiber);
|
||||
}
|
||||
}
|
||||
|
||||
function attachSuspenseRetryListeners(
|
||||
finishedWork: Fiber,
|
||||
wakeables: Set<Wakeable>,
|
||||
@@ -2845,6 +2871,8 @@ function commitMutationEffectsOnFiber(
|
||||
}
|
||||
|
||||
commitReconciliationEffects(finishedWork);
|
||||
// TODO: Add explicit effect flag to set _current.
|
||||
finishedWork.stateNode._current = finishedWork;
|
||||
|
||||
if (flags & Visibility) {
|
||||
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
|
||||
@@ -2871,7 +2899,8 @@ function commitMutationEffectsOnFiber(
|
||||
}
|
||||
}
|
||||
|
||||
if (supportsMutation) {
|
||||
// Offscreen with manual mode manages visibility manually.
|
||||
if (supportsMutation && !isOffscreenManual(finishedWork)) {
|
||||
// TODO: This needs to run whenever there's an insertion or update
|
||||
// inside a hidden Offscreen tree.
|
||||
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
|
||||
|
||||
@@ -25,6 +25,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
|
||||
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
|
||||
import type {Wakeable} from 'shared/ReactTypes';
|
||||
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
|
||||
import type {
|
||||
OffscreenState,
|
||||
OffscreenInstance,
|
||||
@@ -156,6 +157,7 @@ import {
|
||||
clearSingleton,
|
||||
acquireSingletonInstance,
|
||||
releaseSingletonInstance,
|
||||
scheduleMicrotask,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {
|
||||
captureCommitPhaseError,
|
||||
@@ -172,6 +174,7 @@ import {
|
||||
setIsRunningInsertionEffect,
|
||||
getExecutionContext,
|
||||
CommitContext,
|
||||
RenderContext,
|
||||
NoContext,
|
||||
} from './ReactFiberWorkLoop.old';
|
||||
import {
|
||||
@@ -200,6 +203,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.old';
|
||||
import {clearTransitionsForLanes} from './ReactFiberLane.old';
|
||||
import {
|
||||
OffscreenVisible,
|
||||
OffscreenDetached,
|
||||
OffscreenPassiveEffectsConnected,
|
||||
} from './ReactFiberOffscreenComponent';
|
||||
import {
|
||||
@@ -2416,6 +2420,28 @@ function getRetryCache(finishedWork) {
|
||||
}
|
||||
}
|
||||
|
||||
export function detachOffscreenInstance(instance: OffscreenInstance): void {
|
||||
const currentOffscreenFiber = instance._current;
|
||||
if (currentOffscreenFiber === null) {
|
||||
throw new Error(
|
||||
'Calling Offscreen.detach before instance handle has been set.',
|
||||
);
|
||||
}
|
||||
|
||||
const executionContext = getExecutionContext();
|
||||
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
|
||||
scheduleMicrotask(() => {
|
||||
instance._visibility |= OffscreenDetached;
|
||||
disappearLayoutEffects(currentOffscreenFiber);
|
||||
disconnectPassiveEffect(currentOffscreenFiber);
|
||||
});
|
||||
} else {
|
||||
instance._visibility |= OffscreenDetached;
|
||||
disappearLayoutEffects(currentOffscreenFiber);
|
||||
disconnectPassiveEffect(currentOffscreenFiber);
|
||||
}
|
||||
}
|
||||
|
||||
function attachSuspenseRetryListeners(
|
||||
finishedWork: Fiber,
|
||||
wakeables: Set<Wakeable>,
|
||||
@@ -2845,6 +2871,8 @@ function commitMutationEffectsOnFiber(
|
||||
}
|
||||
|
||||
commitReconciliationEffects(finishedWork);
|
||||
// TODO: Add explicit effect flag to set _current.
|
||||
finishedWork.stateNode._current = finishedWork;
|
||||
|
||||
if (flags & Visibility) {
|
||||
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
|
||||
@@ -2871,7 +2899,8 @@ function commitMutationEffectsOnFiber(
|
||||
}
|
||||
}
|
||||
|
||||
if (supportsMutation) {
|
||||
// Offscreen with manual mode manages visibility manually.
|
||||
if (supportsMutation && !isOffscreenManual(finishedWork)) {
|
||||
// TODO: This needs to run whenever there's an insertion or update
|
||||
// inside a hidden Offscreen tree.
|
||||
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
SuspenseState,
|
||||
SuspenseListRenderState,
|
||||
} from './ReactFiberSuspenseComponent.new';
|
||||
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
|
||||
import type {OffscreenState} from './ReactFiberOffscreenComponent';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
||||
import type {Cache} from './ReactFiberCacheComponent.new';
|
||||
@@ -425,7 +426,14 @@ if (supportsMutation) {
|
||||
if (child !== null) {
|
||||
child.return = node;
|
||||
}
|
||||
appendAllChildrenToContainer(containerChildSet, node, true, true);
|
||||
// If Offscreen is not in manual mode, detached tree is hidden from user space.
|
||||
const _needsVisibilityToggle = !isOffscreenManual(node);
|
||||
appendAllChildrenToContainer(
|
||||
containerChildSet,
|
||||
node,
|
||||
_needsVisibilityToggle,
|
||||
true,
|
||||
);
|
||||
} else if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
SuspenseState,
|
||||
SuspenseListRenderState,
|
||||
} from './ReactFiberSuspenseComponent.old';
|
||||
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
|
||||
import type {OffscreenState} from './ReactFiberOffscreenComponent';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
|
||||
import type {Cache} from './ReactFiberCacheComponent.old';
|
||||
@@ -425,7 +426,14 @@ if (supportsMutation) {
|
||||
if (child !== null) {
|
||||
child.return = node;
|
||||
}
|
||||
appendAllChildrenToContainer(containerChildSet, node, true, true);
|
||||
// If Offscreen is not in manual mode, detached tree is hidden from user space.
|
||||
const _needsVisibilityToggle = !isOffscreenManual(node);
|
||||
appendAllChildrenToContainer(
|
||||
containerChildSet,
|
||||
node,
|
||||
_needsVisibilityToggle,
|
||||
true,
|
||||
);
|
||||
} else if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import type {ReactNodeList, OffscreenMode, Wakeable} from 'shared/ReactTypes';
|
||||
import type {Lanes} from './ReactFiberLane.old';
|
||||
import type {SpawnedCachePool} from './ReactFiberCacheComponent.new';
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {
|
||||
Transition,
|
||||
TracingMarkerInstance,
|
||||
@@ -44,8 +45,9 @@ export type OffscreenQueue = {
|
||||
|
||||
type OffscreenVisibility = number;
|
||||
|
||||
export const OffscreenVisible = /* */ 0b01;
|
||||
export const OffscreenPassiveEffectsConnected = /* */ 0b10;
|
||||
export const OffscreenVisible = /* */ 0b001;
|
||||
export const OffscreenDetached = /* */ 0b010;
|
||||
export const OffscreenPassiveEffectsConnected = /* */ 0b100;
|
||||
|
||||
export type OffscreenInstance = {
|
||||
_visibility: OffscreenVisibility,
|
||||
@@ -53,4 +55,17 @@ export type OffscreenInstance = {
|
||||
_transitions: Set<Transition> | null,
|
||||
// $FlowFixMe[incompatible-type-arg] found when upgrading Flow
|
||||
_retryCache: WeakSet<Wakeable> | Set<Wakeable> | null,
|
||||
|
||||
// Represents the current Offscreen fiber
|
||||
_current: Fiber | null,
|
||||
detach: () => void,
|
||||
|
||||
// TODO: attach
|
||||
};
|
||||
|
||||
export function isOffscreenManual(offscreenFiber: Fiber): boolean {
|
||||
return (
|
||||
offscreenFiber.memoizedProps !== null &&
|
||||
offscreenFiber.memoizedProps.mode === 'manual'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ type ExecutionContext = number;
|
||||
|
||||
export const NoContext = /* */ 0b000;
|
||||
const BatchedContext = /* */ 0b001;
|
||||
const RenderContext = /* */ 0b010;
|
||||
export const RenderContext = /* */ 0b010;
|
||||
export const CommitContext = /* */ 0b100;
|
||||
|
||||
type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
|
||||
@@ -290,7 +290,7 @@ type ExecutionContext = number;
|
||||
|
||||
export const NoContext = /* */ 0b000;
|
||||
const BatchedContext = /* */ 0b001;
|
||||
const RenderContext = /* */ 0b010;
|
||||
export const RenderContext = /* */ 0b010;
|
||||
export const CommitContext = /* */ 0b100;
|
||||
|
||||
type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
|
||||
+230
-1
@@ -1362,6 +1362,200 @@ describe('ReactOffscreen', () => {
|
||||
|
||||
expect(offscreenRef.current).not.toBeNull();
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
it('should lower update priority for detached Offscreen', async () => {
|
||||
let updateChildState;
|
||||
let updateHighPriorityComponentState;
|
||||
let offscreenRef;
|
||||
|
||||
function Child() {
|
||||
const [state, _stateUpdate] = useState(0);
|
||||
updateChildState = _stateUpdate;
|
||||
const text = 'Child ' + state;
|
||||
return <Text text={text} />;
|
||||
}
|
||||
|
||||
function HighPriorityComponent(props) {
|
||||
const [state, _stateUpdate] = useState(0);
|
||||
updateHighPriorityComponentState = _stateUpdate;
|
||||
const text = 'HighPriorityComponent ' + state;
|
||||
return (
|
||||
<>
|
||||
<Text text={text} />
|
||||
{props.children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
offscreenRef = useRef(null);
|
||||
return (
|
||||
<>
|
||||
<HighPriorityComponent>
|
||||
<Offscreen mode={'manual'} ref={offscreenRef}>
|
||||
<Child />
|
||||
</Offscreen>
|
||||
</HighPriorityComponent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 0" />
|
||||
<span prop="Child 0" />
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(offscreenRef.current).not.toBeNull();
|
||||
expect(offscreenRef.current.detach).not.toBeNull();
|
||||
|
||||
// Offscreen is attached by default. State updates from offscreen are **not defered**.
|
||||
await act(async () => {
|
||||
updateChildState(1);
|
||||
updateHighPriorityComponentState(1);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'HighPriorityComponent 1',
|
||||
'Child 1',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 1" />
|
||||
<span prop="Child 1" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// detaching offscreen.
|
||||
offscreenRef.current.detach();
|
||||
|
||||
// Offscreen is detached. State updates from offscreen are **defered**.
|
||||
await act(async () => {
|
||||
updateChildState(2);
|
||||
updateHighPriorityComponentState(2);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 2']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 2" />
|
||||
<span prop="Child 1" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Child 2']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 2" />
|
||||
<span prop="Child 2" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
it('defers detachment if called during commit', async () => {
|
||||
let updateChildState;
|
||||
let updateHighPriorityComponentState;
|
||||
let offscreenRef;
|
||||
let nextRenderTriggerDetach = false;
|
||||
|
||||
function Child() {
|
||||
const [state, _stateUpdate] = useState(0);
|
||||
updateChildState = _stateUpdate;
|
||||
const text = 'Child ' + state;
|
||||
return <Text text={text} />;
|
||||
}
|
||||
|
||||
function HighPriorityComponent(props) {
|
||||
const [state, _stateUpdate] = useState(0);
|
||||
updateHighPriorityComponentState = _stateUpdate;
|
||||
const text = 'HighPriorityComponent ' + state;
|
||||
useLayoutEffect(() => {
|
||||
if (nextRenderTriggerDetach) {
|
||||
offscreenRef.current.detach();
|
||||
_stateUpdate(state + 1);
|
||||
updateChildState(state + 1);
|
||||
nextRenderTriggerDetach = false;
|
||||
}
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Text text={text} />
|
||||
{props.children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
offscreenRef = useRef(null);
|
||||
return (
|
||||
<>
|
||||
<HighPriorityComponent>
|
||||
<Offscreen mode={'manual'} ref={offscreenRef}>
|
||||
<Child />
|
||||
</Offscreen>
|
||||
</HighPriorityComponent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
|
||||
|
||||
nextRenderTriggerDetach = true;
|
||||
|
||||
// Offscreen is attached. State updates from offscreen are **not defered**.
|
||||
// Offscreen is detached inside useLayoutEffect;
|
||||
await act(async () => {
|
||||
updateChildState(1);
|
||||
updateHighPriorityComponentState(1);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'HighPriorityComponent 1',
|
||||
'Child 1',
|
||||
'HighPriorityComponent 2',
|
||||
'Child 2',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 2" />
|
||||
<span prop="Child 2" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// Offscreen is detached. State updates from offscreen are **defered**.
|
||||
await act(async () => {
|
||||
updateChildState(3);
|
||||
updateHighPriorityComponentState(3);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 3']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 3" />
|
||||
<span prop="Child 2" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Child 3']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 3" />
|
||||
<span prop="Child 3" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -1430,7 +1624,6 @@ describe('ReactOffscreen', () => {
|
||||
});
|
||||
|
||||
expect(offscreenRef.current).not.toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
root.render(<App mode={'hidden'} />);
|
||||
});
|
||||
@@ -1438,5 +1631,41 @@ describe('ReactOffscreen', () => {
|
||||
expect(offscreenRef.current).toBeNull();
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
it('should change _current', async () => {
|
||||
let offscreenRef;
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
function App({children}) {
|
||||
offscreenRef = useRef(null);
|
||||
return (
|
||||
<Offscreen mode={'manual'} ref={offscreenRef}>
|
||||
{children}
|
||||
</Offscreen>
|
||||
);
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<App>
|
||||
<div />
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(offscreenRef.current).not.toBeNull();
|
||||
const firstFiber = offscreenRef.current._current;
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<App>
|
||||
<span />
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(offscreenRef.current._current === firstFiber).toBeFalsy();
|
||||
});
|
||||
|
||||
// TODO: When attach/detach methods are implemented. Add tests for nested Offscreen case.
|
||||
});
|
||||
|
||||
@@ -440,5 +440,6 @@
|
||||
"452": "React expected an <html> element (document.documentElement) to exist in the Document but one was not found. React never removes the documentElement for any Document it renders into so the cause is likely in some other script running on this page.",
|
||||
"453": "React expected a <head> element (document.head) to exist in the Document but one was not found. React never removes the head for any Document it renders into so the cause is likely in some other script running on this page.",
|
||||
"454": "React expected a <body> element (document.body) to exist in the Document but one was not found. React never removes the body for any Document it renders into so the cause is likely in some other script running on this page.",
|
||||
"455": "This CacheSignal was requested outside React which means that it is immediately aborted."
|
||||
}
|
||||
"455": "This CacheSignal was requested outside React which means that it is immediately aborted.",
|
||||
"456": "Calling Offscreen.detach before instance handle has been set."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user