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

feat(StrictMode): Double-invoke render for every component #18430

Merged
merged 2 commits into from Mar 29, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/react-art/src/__tests__/ReactART-test.js
Expand Up @@ -391,7 +391,7 @@ describe('ReactART', () => {
</CurrentRendererContext.Provider>,
);

expect(Scheduler).toFlushAndYieldThrough(['A']);
expect(Scheduler).toFlushAndYieldThrough(__DEV__ ? ['A', 'A'] : ['A']);

ReactDOM.render(
<Surface>
Expand All @@ -406,7 +406,9 @@ describe('ReactART', () => {
expect(ops).toEqual([null, 'ART']);

ops = [];
expect(Scheduler).toFlushAndYield(['B', 'C']);
expect(Scheduler).toFlushAndYield(
__DEV__ ? ['B', 'B', 'C', 'C'] : ['B', 'C'],
);

expect(ops).toEqual(['Test']);
});
Expand Down
Expand Up @@ -639,7 +639,9 @@ describe('ReactDOMFiberAsync', () => {
expect(container.textContent).toEqual('');

// Everything should render immediately in the next event
expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
expect(Scheduler).toFlushExpired(
__DEV__ ? ['A', 'A', 'B', 'B', 'C', 'C'] : ['A', 'B', 'C'],
);
expect(container.textContent).toEqual('ABC');
});
});
Expand Down
4 changes: 3 additions & 1 deletion packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
Expand Up @@ -607,7 +607,9 @@ function runActTests(label, render, unmount, rerender) {
},
);

expect(Component).toHaveBeenCalledTimes(4);
expect(Component).toHaveBeenCalledTimes(
label === 'legacy mode' ? 4 : 8,
);
unmount(secondContainer);
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/src/__tests__/ReactUpdates-test.js
Expand Up @@ -1333,6 +1333,7 @@ describe('ReactUpdates', () => {
'Foo',
'Foo',
'Baz',
'Baz',
'Foo#effect',
]);
} else {
Expand Down
Expand Up @@ -813,7 +813,7 @@ describe('DOMEventResponderSystem', () => {

let root = ReactDOM.createRoot(container);
root.render(<Test counter={0} />);
expect(Scheduler).toFlushAndYield(['Test']);
expect(Scheduler).toFlushAndYield(__DEV__ ? ['Test', 'Test'] : ['Test']);

// Click the button
dispatchClickEvent(ref.current);
Expand All @@ -825,7 +825,9 @@ describe('DOMEventResponderSystem', () => {
// Increase counter
root.render(<Test counter={1} />);
// Yield before committing
expect(Scheduler).toFlushAndYieldThrough(['Test']);
expect(Scheduler).toFlushAndYieldThrough(
__DEV__ ? ['Test', 'Test'] : ['Test'],
);

// Click the button again
dispatchClickEvent(ref.current);
Expand Down
76 changes: 32 additions & 44 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Expand Up @@ -321,17 +321,14 @@ function updateForwardRef(
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode
) {
// Only double-render components with Hooks
if (workInProgress.memoizedState !== null) {
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderExpirationTime,
);
}
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderExpirationTime,
);
}
setIsRendering(false);
} else {
Expand Down Expand Up @@ -662,17 +659,14 @@ function updateFunctionComponent(
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode
) {
// Only double-render components with Hooks
if (workInProgress.memoizedState !== null) {
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderExpirationTime,
);
}
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderExpirationTime,
);
}
setIsRendering(false);
} else {
Expand Down Expand Up @@ -738,17 +732,14 @@ function updateBlock<Props, Data>(
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode
) {
// Only double-render components with Hooks
if (workInProgress.memoizedState !== null) {
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
data,
renderExpirationTime,
);
}
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
data,
renderExpirationTime,
);
}
setIsRendering(false);
} else {
Expand Down Expand Up @@ -1471,17 +1462,14 @@ function mountIndeterminateComponent(
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode
) {
// Only double-render components with Hooks
if (workInProgress.memoizedState !== null) {
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderExpirationTime,
);
}
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderExpirationTime,
);
}
}
reconcileChildren(null, workInProgress, value, renderExpirationTime);
Expand Down
Expand Up @@ -12,6 +12,7 @@ describe('ErrorBoundaryReconciliation', () => {
jest.resetModules();

ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactTestRenderer = require('react-test-renderer');
React = require('react');
Expand Down
Expand Up @@ -1300,14 +1300,14 @@ describe('ReactHooks', () => {
<NoHooks />
</StrictMode>,
);
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);
renderCount = 0;
renderer.update(
<StrictMode>
<NoHooks />
</StrictMode>,
);
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);

renderCount = 0;
renderer.update(<FwdRef />);
Expand All @@ -1321,14 +1321,14 @@ describe('ReactHooks', () => {
<FwdRef />
</StrictMode>,
);
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);
renderCount = 0;
renderer.update(
<StrictMode>
<FwdRef />
</StrictMode>,
);
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);

renderCount = 0;
renderer.update(<Memo arg={1} />);
Expand All @@ -1342,14 +1342,14 @@ describe('ReactHooks', () => {
<Memo arg={1} />
</StrictMode>,
);
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);
renderCount = 0;
renderer.update(
<StrictMode>
<Memo arg={2} />
</StrictMode>,
);
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);

renderCount = 0;
expect(() => renderer.update(<Factory />)).toErrorDev(
Expand Down
Expand Up @@ -1103,7 +1103,9 @@ describe('ReactNewContext', () => {

// Render the provider again using a different renderer
ReactNoop.render(<App value={1} />);
expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
expect(Scheduler).toFlushAndYield(
__DEV__ ? ['Foo', 'Foo', 'Foo', 'Foo'] : ['Foo', 'Foo'],
);

if (__DEV__) {
expect(console.error.calls.argsFor(0)[0]).toContain(
Expand Down
Expand Up @@ -11,12 +11,15 @@
'use strict';

let React;
let ReactFeatureFlags;
let ReactTestRenderer;
let Scheduler;

describe('ReactTestRendererAsync', () => {
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
React = require('react');
ReactTestRenderer = require('react-test-renderer');
Scheduler = require('scheduler');
Expand Down
Expand Up @@ -188,7 +188,7 @@ describe('ReactProfiler DevTools integration', () => {

// Commit something
root.update(<Text text="A" />);
expect(Scheduler).toFlushAndYield(['A']);
expect(Scheduler).toFlushAndYield(__DEV__ ? ['A', 'A'] : ['A']);
expect(root).toMatchRenderedOutput('A');

// Advance time by many seconds, larger than the default expiration time
Expand All @@ -200,7 +200,7 @@ describe('ReactProfiler DevTools integration', () => {
// Update B should not instantly expire.
expect(Scheduler).toFlushExpired([]);

expect(Scheduler).toFlushAndYield(['B']);
expect(Scheduler).toFlushAndYield(__DEV__ ? ['B', 'B'] : ['B']);
expect(root).toMatchRenderedOutput('B');
});
});
28 changes: 14 additions & 14 deletions packages/react/src/__tests__/forwardRef-test.js
Expand Up @@ -260,11 +260,11 @@ describe('forwardRef', () => {

ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);

ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(2);
expect(renderCount).toBe(__DEV__ ? 4 : 2);
});

it('should bailout if forwardRef is wrapped in memo', () => {
Expand All @@ -283,28 +283,28 @@ describe('forwardRef', () => {

ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);

expect(ref.current.type).toBe('div');

ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);

const differentRef = React.createRef();

ReactNoop.render(
<RefForwardingComponent ref={differentRef} optional="foo" />,
);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(2);
expect(renderCount).toBe(__DEV__ ? 4 : 2);

expect(ref.current).toBe(null);
expect(differentRef.current.type).toBe('div');

ReactNoop.render(<RefForwardingComponent ref={ref} optional="bar" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(3);
expect(renderCount).toBe(__DEV__ ? 6 : 3);
});

it('should custom memo comparisons to compose', () => {
Expand All @@ -324,19 +324,19 @@ describe('forwardRef', () => {

ReactNoop.render(<RefForwardingComponent ref={ref} a="0" b="0" c="1" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(1);
expect(renderCount).toBe(__DEV__ ? 2 : 1);

expect(ref.current.type).toBe('div');

// Changing either a or b rerenders
ReactNoop.render(<RefForwardingComponent ref={ref} a="0" b="1" c="1" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(2);
expect(renderCount).toBe(__DEV__ ? 4 : 2);

// Changing c doesn't rerender
ReactNoop.render(<RefForwardingComponent ref={ref} a="0" b="1" c="2" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(2);
expect(renderCount).toBe(__DEV__ ? 4 : 2);

const ComposedMemo = React.memo(
RefForwardingComponent,
Expand All @@ -345,29 +345,29 @@ describe('forwardRef', () => {

ReactNoop.render(<ComposedMemo ref={ref} a="0" b="0" c="0" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(3);
expect(renderCount).toBe(__DEV__ ? 6 : 3);

// Changing just b no longer updates
ReactNoop.render(<ComposedMemo ref={ref} a="0" b="1" c="0" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(3);
expect(renderCount).toBe(__DEV__ ? 6 : 3);

// Changing just a and c updates
ReactNoop.render(<ComposedMemo ref={ref} a="2" b="2" c="2" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(4);
expect(renderCount).toBe(__DEV__ ? 8 : 4);

// Changing just c does not update
ReactNoop.render(<ComposedMemo ref={ref} a="2" b="2" c="3" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(4);
expect(renderCount).toBe(__DEV__ ? 8 : 4);

// Changing ref still rerenders
const differentRef = React.createRef();

ReactNoop.render(<ComposedMemo ref={differentRef} a="2" b="2" c="3" />);
expect(Scheduler).toFlushWithoutYielding();
expect(renderCount).toBe(5);
expect(renderCount).toBe(__DEV__ ? 10 : 5);

expect(ref.current).toBe(null);
expect(differentRef.current.type).toBe('div');
Expand Down