Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -1215,7 +1215,7 @@ describe('ReactFabric', () => {
}}
/>
<View
ref={ref2}
ref={ref3}
id="explicitTimeStampLowerCase"
onTouchEnd={event => {
expect(event.timeStamp).toBe(explicitTimeStampLowerCase);
Expand Down
7 changes: 5 additions & 2 deletions packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,8 +524,11 @@ function createChildReconciler(
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
newFiber.flags |= Placement | PlacementDEV;
// This is a move. The fiber already exists and is being repositioned in
// the DOM. We set Placement so the host node is moved, but we do NOT
// set PlacementDEV because the component is not newly mounting —
// StrictMode should not double-invoke effects for moved components.
newFiber.flags |= Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
Expand Down
4 changes: 2 additions & 2 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,8 @@ function areHookInputsEqual(
'Previous: %s\n' +
'Incoming: %s',
currentHookNameInDev,
`[${prevDeps.join(', ')}]`,
`[${nextDeps.join(', ')}]`,
`[${prevDeps.map(String).join(', ')}]`,
`[${nextDeps.map(String).join(', ')}]`,
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,33 @@ describe('ReactHooks', () => {
]);
});

it('warns about variable number of dependencies when deps contain Symbols', async () => {
const {useLayoutEffect} = React;
const sym = Symbol('testSymbol');
function App(props) {
useLayoutEffect(() => {}, props.dependencies);
return null;
}
let root;
await act(() => {
root = ReactTestRenderer.create(<App dependencies={[sym]} />, {
unstable_isConcurrent: true,
});
});
await act(() => {
root.update(<App dependencies={[sym, 'extra']} />);
});
assertConsoleErrorDev([
'The final argument passed to useLayoutEffect changed size ' +
'between renders. The order and size of this array must remain ' +
'constant.\n' +
'\n' +
'Previous: [Symbol(testSymbol)]\n' +
'Incoming: [Symbol(testSymbol), extra]\n' +
' in App (at **)',
]);
});

it('warns if switching from dependencies to no dependencies', async () => {
const {useMemo} = React;
function App({text, hasDeps}) {
Expand Down
48 changes: 48 additions & 0 deletions packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -964,4 +964,52 @@ describe('StrictEffectsMode', () => {
'Child dep create',
]);
});

it('should not double invoke effects when a keyed child is moved in an array', async () => {
const log = [];

function Item({id}) {
React.useEffect(() => {
log.push(`${id} effect mount`);
return () => log.push(`${id} effect unmount`);
}, []);

React.useLayoutEffect(() => {
log.push(`${id} layout mount`);
return () => log.push(`${id} layout unmount`);
}, []);

return id;
}

const root = ReactNoop.createRoot();

// Initial render: [A, B, C]
await act(() => {
root.render(
<React.StrictMode>
{['A', 'B', 'C'].map(id => (
<Item key={id} id={id} />
))}
</React.StrictMode>,
);
});

log.length = 0; // clear mount logs, only care about what happens on reorder

// Reorder to [C, A, B] — all elements move but none are new
await act(() => {
root.render(
<React.StrictMode>
{['C', 'A', 'B'].map(id => (
<Item key={id} id={id} />
))}
</React.StrictMode>,
);
});

// Moved elements should not have their effects re-run.
// Only DOM placement happens; no mount/unmount of effects.
expect(log).toEqual([]);
});
});
4 changes: 2 additions & 2 deletions packages/react-server/src/ReactFizzHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ function areHookInputsEqual(
'Previous: %s\n' +
'Incoming: %s',
currentHookNameInDev,
`[${nextDeps.join(', ')}]`,
`[${prevDeps.join(', ')}]`,
`[${nextDeps.map(String).join(', ')}]`,
`[${prevDeps.map(String).join(', ')}]`,
);
}
}
Expand Down