Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Expose ref to Offscreen if mode is manual
  • Loading branch information
sammy-SC committed Sep 13, 2022
commit a86f80df7d0517bd585443d4a4c3f2ac9cbafa3b
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,10 @@ function updateOffscreenComponent(
const prevState: OffscreenState | null =
current !== null ? current.memoizedState : null;

if (nextProps.mode === 'manual') {
markRef(current, workInProgress);
}

if (
nextProps.mode === 'hidden' ||
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
Expand Down
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,10 @@ function updateOffscreenComponent(
const prevState: OffscreenState | null =
current !== null ? current.memoizedState : null;

if (nextProps.mode === 'manual') {
markRef(current, workInProgress);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it switches from manual to something else? We need to schedule a Ref effect to detach it. I think the test you wrote happens to work because it also schedules a Visibility effect during the same render.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was playing around with this and couldn't come up with a test case to break this.

Anyway, I'm going to remove the condition to only call markRef if mode is manual.


if (
nextProps.mode === 'hidden' ||
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
Expand Down
8 changes: 8 additions & 0 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,14 @@ function commitLayoutEffectOnFiber(
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
}

if (finishedWork.pendingProps.mode === 'manual') {
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
} else if (finishedWork.pendingProps.mode !== undefined) {
safelyDetachRef(finishedWork, finishedWork.return);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs to be wrapped in a Ref effect. Otherwise you might forget to schedule a Ref effect in the begin phase, but it sometimes works anyway accidentally because you happen to have another effect flag scheduled during the same commit.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I think this should be a regular else block. Not else if. Anything that's not manual should have its ref detached.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs to be wrapped in a Ref effect. Otherwise you might forget to schedule a Ref effect in the begin phase, but it sometimes works anyway accidentally because you happen to have another effect flag scheduled during the same commit.

I don't follow this. What do you mean it needs to be wrapped in a Ref effect?

}
} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
Expand Down
8 changes: 8 additions & 0 deletions packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,14 @@ function commitLayoutEffectOnFiber(
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
}

if (finishedWork.pendingProps.mode === 'manual') {
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
} else if (finishedWork.pendingProps.mode !== undefined) {
safelyDetachRef(finishedWork, finishedWork.return);
}
} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
Expand Down
48 changes: 48 additions & 0 deletions packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let useState;
let useLayoutEffect;
let useEffect;
let useMemo;
let useRef;
let startTransition;

describe('ReactOffscreen', () => {
Expand All @@ -24,6 +25,7 @@ describe('ReactOffscreen', () => {
useLayoutEffect = React.useLayoutEffect;
useEffect = React.useEffect;
useMemo = React.useMemo;
useRef = React.useRef;
startTransition = React.startTransition;
});

Expand Down Expand Up @@ -1259,4 +1261,50 @@ describe('ReactOffscreen', () => {
</div>,
);
});

describe('manual interactivity', () => {
// @gate enableOffscreen
it('should attach ref only for mode null', async () => {
let offscreenRef;

function App({mode}) {
offscreenRef = useRef(null);
return (
<Offscreen
mode={mode}
ref={ref => {
offscreenRef.current = ref;
}}>
<div />
</Offscreen>
);
}

const root = ReactNoop.createRoot();

await act(async () => {
root.render(<App mode={'manual'} />);
});

expect(offscreenRef.current).not.toBeNull();

await act(async () => {
root.render(<App mode={'visible'} />);
});

expect(offscreenRef.current).toBeNull();

await act(async () => {
root.render(<App mode={'hidden'} />);
});

expect(offscreenRef.current).toBeNull();

await act(async () => {
root.render(<App mode={'manual'} />);
});

expect(offscreenRef.current).not.toBeNull();
});
});
});
3 changes: 2 additions & 1 deletion packages/shared/ReactTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ export type Thenable<T> =
export type OffscreenMode =
| 'hidden'
| 'unstable-defer-without-hiding'
| 'visible';
| 'visible'
| 'manual';

export type StartTransitionOptions = {
name?: string,
Expand Down