Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move all markRef calls into begin phase #28375

Merged
merged 1 commit into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,8 @@ function updateProfiler(
}

function markRef(current: Fiber | null, workInProgress: Fiber) {
// TODO: This is also where we should check the type of the ref and error if
// an invalid one is passed, instead of during child reconcilation.
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
Expand Down Expand Up @@ -3531,7 +3533,7 @@ function updateScopeComponent(
) {
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;

markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
Expand Down
38 changes: 4 additions & 34 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ import {
} from './ReactWorkTags';
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
import {
Ref,
RefStatic,
Placement,
Update,
Visibility,
Expand Down Expand Up @@ -186,10 +184,6 @@ function markUpdate(workInProgress: Fiber) {
workInProgress.flags |= Update;
}

function markRef(workInProgress: Fiber) {
workInProgress.flags |= Ref | RefStatic;
}

/**
* In persistent mode, return whether this update needs to clone the subtree.
*/
Expand Down Expand Up @@ -1083,9 +1077,6 @@ function completeWork(
// @TODO refactor this block to create the instance here in complete
// phase if we are not hydrating.
markUpdate(workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
if (nextResource !== null) {
// This is a Hoistable Resource

Expand Down Expand Up @@ -1120,9 +1111,6 @@ function completeWork(
// and require an update
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
Expand Down Expand Up @@ -1194,10 +1182,6 @@ function completeWork(
renderLanes,
);
}

if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
Expand Down Expand Up @@ -1232,11 +1216,6 @@ function completeWork(
workInProgress.stateNode = instance;
markUpdate(workInProgress);
}

if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
Expand All @@ -1254,10 +1233,6 @@ function completeWork(
newProps,
renderLanes,
);

if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
Expand Down Expand Up @@ -1310,11 +1285,6 @@ function completeWork(
markUpdate(workInProgress);
}
}

if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);

Expand Down Expand Up @@ -1739,16 +1709,16 @@ function completeWork(
workInProgress.stateNode = scopeInstance;
prepareScopeUpdate(scopeInstance, workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
// Scope components always do work in the commit phase if there's a
// ref attached.
markUpdate(workInProgress);
}
} else {
if (workInProgress.ref !== null) {
// Scope components always do work in the commit phase if there's a
// ref attached.
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
Expand Down
83 changes: 83 additions & 0 deletions packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/

'use strict';

let React;
let Scheduler;
let ReactNoop;
let act;
let assertLog;

describe('ReactFiberRefs', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
Scheduler = require('scheduler');
ReactNoop = require('react-noop-renderer');
act = require('internal-test-utils').act;
assertLog = require('internal-test-utils').assertLog;
});

test('ref is attached even if there are no other updates (class)', async () => {
let component;
class Component extends React.PureComponent {
render() {
Scheduler.log('Render');
component = this;
return 'Hi';
}
}

const ref1 = React.createRef();
const ref2 = React.createRef();
const root = ReactNoop.createRoot();

// Mount with ref1 attached
await act(() => root.render(<Component ref={ref1} />));
assertLog(['Render']);
expect(root).toMatchRenderedOutput('Hi');
expect(ref1.current).toBe(component);
// ref2 has no value
expect(ref2.current).toBe(null);

// Switch to ref2, but don't update anything else.
await act(() => root.render(<Component ref={ref2} />));
// The component did not re-render because no props changed.
assertLog([]);
expect(root).toMatchRenderedOutput('Hi');
// But the refs still should have been swapped.
expect(ref1.current).toBe(null);
expect(ref2.current).toBe(component);
});

test('ref is attached even if there are no other updates (host component)', async () => {
// This is kind of ailly test because host components never bail out if they
// receive a new element, and there's no way to update a ref without also
// updating the props, but adding it here anyway for symmetry with the
// class case above.
const ref1 = React.createRef();
const ref2 = React.createRef();
const root = ReactNoop.createRoot();

// Mount with ref1 attached
await act(() => root.render(<div ref={ref1}>Hi</div>));
expect(root).toMatchRenderedOutput(<div>Hi</div>);
expect(ref1.current).not.toBe(null);
// ref2 has no value
expect(ref2.current).toBe(null);

// Switch to ref2, but don't update anything else.
await act(() => root.render(<div ref={ref2}>Hi</div>));
expect(root).toMatchRenderedOutput(<div>Hi</div>);
// But the refs still should have been swapped.
expect(ref1.current).toBe(null);
expect(ref2.current).not.toBe(null);
});
});