Compare commits
6 Commits
eslint-plu
...
eslint-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1727fbf98 | ||
|
|
bc249804d3 | ||
|
|
da9325b519 | ||
|
|
67e47593b6 | ||
|
|
23fcd7cea1 | ||
|
|
bf45a68dd3 |
@@ -167,10 +167,6 @@ jobs:
|
|||||||
# Delete the OSS renderers, these are sync'd to RN separately.
|
# Delete the OSS renderers, these are sync'd to RN separately.
|
||||||
RENDERER_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/implementations/
|
RENDERER_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/implementations/
|
||||||
rm $RENDERER_FOLDER/ReactFabric-{dev,prod,profiling}.js
|
rm $RENDERER_FOLDER/ReactFabric-{dev,prod,profiling}.js
|
||||||
|
|
||||||
# Delete the legacy renderer shim, this is not sync'd and will get deleted in the future.
|
|
||||||
SHIM_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/shims/
|
|
||||||
rm $SHIM_FOLDER/ReactNative.js
|
|
||||||
|
|
||||||
# Copy eslint-plugin-react-hooks
|
# Copy eslint-plugin-react-hooks
|
||||||
# NOTE: This is different from www, here we include the full package
|
# NOTE: This is different from www, here we include the full package
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const canaryChannelLabel = 'canary';
|
|||||||
const rcNumber = 0;
|
const rcNumber = 0;
|
||||||
|
|
||||||
const stablePackages = {
|
const stablePackages = {
|
||||||
'eslint-plugin-react-hooks': '7.1.0',
|
'eslint-plugin-react-hooks': '7.1.1',
|
||||||
'jest-react': '0.18.0',
|
'jest-react': '0.18.0',
|
||||||
react: ReactVersion,
|
react: ReactVersion,
|
||||||
'react-art': ReactVersion,
|
'react-art': ReactVersion,
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
## 7.1.1
|
||||||
|
|
||||||
|
**Note:** 7.1.0 accidentally removed the `component-hook-factories` rule, causing errors for users who referenced it in their ESLint config. This is now fixed.
|
||||||
|
|
||||||
|
- Add deprecated no-op `component-hook-factories` rule for backwards compatibility. ([@mofeiZ](https://github.com/mofeiZ) in [#36307](https://github.com/facebook/react/pull/36307))
|
||||||
|
|
||||||
## 7.1.0
|
## 7.1.0
|
||||||
|
|
||||||
This release adds ESLint v10 support, improves performance by skipping compilation for non-React files, and includes compiler lint improvements including better `set-state-in-effect` detection, improved ref validation, and more helpful error reporting.
|
This release adds ESLint v10 support, improves performance by skipping compilation for non-React files, and includes compiler lint improvements including better `set-state-in-effect` detection, improved ref validation, and more helpful error reporting.
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export default [
|
|||||||
// React Compiler rules
|
// React Compiler rules
|
||||||
'react-hooks/config': 'error',
|
'react-hooks/config': 'error',
|
||||||
'react-hooks/error-boundaries': 'error',
|
'react-hooks/error-boundaries': 'error',
|
||||||
'react-hooks/component-hook-factories': 'error',
|
|
||||||
'react-hooks/gating': 'error',
|
'react-hooks/gating': 'error',
|
||||||
'react-hooks/globals': 'error',
|
'react-hooks/globals': 'error',
|
||||||
'react-hooks/immutability': 'error',
|
'react-hooks/immutability': 'error',
|
||||||
@@ -108,7 +107,6 @@ export default [
|
|||||||
// React Compiler rules
|
// React Compiler rules
|
||||||
"react-hooks/config": "error",
|
"react-hooks/config": "error",
|
||||||
"react-hooks/error-boundaries": "error",
|
"react-hooks/error-boundaries": "error",
|
||||||
"react-hooks/component-hook-factories": "error",
|
|
||||||
"react-hooks/gating": "error",
|
"react-hooks/gating": "error",
|
||||||
"react-hooks/globals": "error",
|
"react-hooks/globals": "error",
|
||||||
"react-hooks/immutability": "error",
|
"react-hooks/immutability": "error",
|
||||||
|
|||||||
@@ -15,12 +15,29 @@ import {
|
|||||||
} from './shared/ReactCompiler';
|
} from './shared/ReactCompiler';
|
||||||
import RulesOfHooks from './rules/RulesOfHooks';
|
import RulesOfHooks from './rules/RulesOfHooks';
|
||||||
|
|
||||||
|
function makeDeprecatedRule(version: string): Rule.RuleModule {
|
||||||
|
return {
|
||||||
|
meta: {
|
||||||
|
type: 'suggestion',
|
||||||
|
docs: {
|
||||||
|
description: `Deprecated: this rule has been removed in ${version}.`,
|
||||||
|
},
|
||||||
|
schema: [],
|
||||||
|
deprecated: true,
|
||||||
|
},
|
||||||
|
create() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
'exhaustive-deps': ExhaustiveDeps,
|
'exhaustive-deps': ExhaustiveDeps,
|
||||||
'rules-of-hooks': RulesOfHooks,
|
'rules-of-hooks': RulesOfHooks,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(allRules).map(([name, config]) => [name, config.rule]),
|
Object.entries(allRules).map(([name, config]) => [name, config.rule]),
|
||||||
),
|
),
|
||||||
|
'component-hook-factories': makeDeprecatedRule('7.1.0'),
|
||||||
} satisfies Record<string, Rule.RuleModule>;
|
} satisfies Record<string, Rule.RuleModule>;
|
||||||
|
|
||||||
const basicRuleConfigs = {
|
const basicRuleConfigs = {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ let Scheduler;
|
|||||||
let Suspense;
|
let Suspense;
|
||||||
let SuspenseList;
|
let SuspenseList;
|
||||||
let useSyncExternalStore;
|
let useSyncExternalStore;
|
||||||
|
let use;
|
||||||
let act;
|
let act;
|
||||||
let IdleEventPriority;
|
let IdleEventPriority;
|
||||||
let waitForAll;
|
let waitForAll;
|
||||||
@@ -116,6 +117,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||||||
Activity = React.Activity;
|
Activity = React.Activity;
|
||||||
Suspense = React.Suspense;
|
Suspense = React.Suspense;
|
||||||
useSyncExternalStore = React.useSyncExternalStore;
|
useSyncExternalStore = React.useSyncExternalStore;
|
||||||
|
use = React.use;
|
||||||
if (gate(flags => flags.enableSuspenseList)) {
|
if (gate(flags => flags.enableSuspenseList)) {
|
||||||
SuspenseList = React.unstable_SuspenseList;
|
SuspenseList = React.unstable_SuspenseList;
|
||||||
}
|
}
|
||||||
@@ -256,6 +258,77 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||||||
expect(container.textContent).toBe('HelloHello');
|
expect(container.textContent).toBe('HelloHello');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('replays effects when a suspended boundary hydrates in StrictMode', async () => {
|
||||||
|
const log = [];
|
||||||
|
let suspend = false;
|
||||||
|
let resolve;
|
||||||
|
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||||
|
|
||||||
|
function EffectfulChild() {
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
log.push('layout mount');
|
||||||
|
return () => log.push('layout unmount');
|
||||||
|
}, []);
|
||||||
|
React.useEffect(() => {
|
||||||
|
log.push('effect mount');
|
||||||
|
return () => log.push('effect unmount');
|
||||||
|
}, []);
|
||||||
|
return 'Hello';
|
||||||
|
}
|
||||||
|
|
||||||
|
function Child() {
|
||||||
|
if (suspend) {
|
||||||
|
use(promise);
|
||||||
|
}
|
||||||
|
return <EffectfulChild />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback="Loading...">
|
||||||
|
<Child />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
suspend = false;
|
||||||
|
const finalHTML = ReactDOMServer.renderToString(element);
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.innerHTML = finalHTML;
|
||||||
|
expect(container.textContent).toBe('Hello');
|
||||||
|
|
||||||
|
suspend = true;
|
||||||
|
ReactDOMClient.hydrateRoot(container, element);
|
||||||
|
await waitForAll([]);
|
||||||
|
expect(log).toEqual([]);
|
||||||
|
expect(container.textContent).toBe('Hello');
|
||||||
|
|
||||||
|
suspend = false;
|
||||||
|
resolve();
|
||||||
|
await promise;
|
||||||
|
await waitForAll([]);
|
||||||
|
|
||||||
|
expect(container.textContent).toBe('Hello');
|
||||||
|
if (__DEV__) {
|
||||||
|
expect(log).toEqual([
|
||||||
|
'layout mount',
|
||||||
|
'effect mount',
|
||||||
|
'layout unmount',
|
||||||
|
'effect unmount',
|
||||||
|
'layout mount',
|
||||||
|
'effect mount',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
expect(log).toEqual(['layout mount', 'effect mount']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('falls back to client rendering boundary on mismatch', async () => {
|
it('falls back to client rendering boundary on mismatch', async () => {
|
||||||
let client = false;
|
let client = false;
|
||||||
let suspend = false;
|
let suspend = false;
|
||||||
|
|||||||
@@ -392,6 +392,56 @@ describe('ReactDOMServerHydration', () => {
|
|||||||
expect(element.textContent).toBe('Hi');
|
expect(element.textContent).toBe('Hi');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('replays effects when hydrating a StrictMode subtree', async () => {
|
||||||
|
const log = [];
|
||||||
|
function Child() {
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
log.push('layout mount');
|
||||||
|
return () => log.push('layout unmount');
|
||||||
|
}, []);
|
||||||
|
React.useEffect(() => {
|
||||||
|
log.push('effect mount');
|
||||||
|
return () => log.push('effect unmount');
|
||||||
|
}, []);
|
||||||
|
return <span>Hello</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Child />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const markup = (
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = document.createElement('div');
|
||||||
|
element.innerHTML = ReactDOMServer.renderToString(markup);
|
||||||
|
expect(element.textContent).toBe('Hello');
|
||||||
|
|
||||||
|
await act(() => {
|
||||||
|
ReactDOMClient.hydrateRoot(element, markup);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
expect(log).toEqual([
|
||||||
|
'layout mount',
|
||||||
|
'effect mount',
|
||||||
|
'layout unmount',
|
||||||
|
'effect unmount',
|
||||||
|
'layout mount',
|
||||||
|
'effect mount',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
expect(log).toEqual(['layout mount', 'effect mount']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to render and hydrate forwardRef components', async () => {
|
it('should be able to render and hydrate forwardRef components', async () => {
|
||||||
const FunctionComponent = ({label, forwardedRef}) => (
|
const FunctionComponent = ({label, forwardedRef}) => (
|
||||||
<div ref={forwardedRef}>{label}</div>
|
<div ref={forwardedRef}>{label}</div>
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ import {
|
|||||||
NoFlags,
|
NoFlags,
|
||||||
PerformedWork,
|
PerformedWork,
|
||||||
Placement,
|
Placement,
|
||||||
|
PlacementDEV,
|
||||||
Hydrating,
|
Hydrating,
|
||||||
Callback,
|
Callback,
|
||||||
ContentReset,
|
ContentReset,
|
||||||
@@ -1080,7 +1081,8 @@ function updateDehydratedActivityComponent(
|
|||||||
// Conceptually this is similar to Placement in that a new subtree is
|
// Conceptually this is similar to Placement in that a new subtree is
|
||||||
// inserted into the React tree here. It just happens to not need DOM
|
// inserted into the React tree here. It just happens to not need DOM
|
||||||
// mutations because it already exists.
|
// mutations because it already exists.
|
||||||
primaryChildFragment.flags |= Hydrating;
|
// We should still treat it as a newly inserted Fiber to double invoke Strict Effects.
|
||||||
|
primaryChildFragment.flags |= Hydrating | PlacementDEV;
|
||||||
return primaryChildFragment;
|
return primaryChildFragment;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1899,7 +1901,8 @@ function updateHostRoot(
|
|||||||
// Conceptually this is similar to Placement in that a new subtree is
|
// Conceptually this is similar to Placement in that a new subtree is
|
||||||
// inserted into the React tree here. It just happens to not need DOM
|
// inserted into the React tree here. It just happens to not need DOM
|
||||||
// mutations because it already exists.
|
// mutations because it already exists.
|
||||||
node.flags = (node.flags & ~Placement) | Hydrating;
|
// We should still treat it as a newly inserted Fiber to double invoke Strict Effects.
|
||||||
|
node.flags = (node.flags & ~Placement) | Hydrating | PlacementDEV;
|
||||||
node = node.sibling;
|
node = node.sibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3104,7 +3107,8 @@ function updateDehydratedSuspenseComponent(
|
|||||||
// Conceptually this is similar to Placement in that a new subtree is
|
// Conceptually this is similar to Placement in that a new subtree is
|
||||||
// inserted into the React tree here. It just happens to not need DOM
|
// inserted into the React tree here. It just happens to not need DOM
|
||||||
// mutations because it already exists.
|
// mutations because it already exists.
|
||||||
primaryChildFragment.flags |= Hydrating;
|
// We should still treat it as a newly inserted Fiber to double invoke Strict Effects.
|
||||||
|
primaryChildFragment.flags |= Hydrating | PlacementDEV;
|
||||||
return primaryChildFragment;
|
return primaryChildFragment;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -3862,7 +3866,7 @@ function remountFiber(
|
|||||||
deletions.push(current);
|
deletions.push(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
newWorkInProgress.flags |= Placement;
|
newWorkInProgress.flags |= Placement | PlacementDEV;
|
||||||
|
|
||||||
// Restart work from the new fiber.
|
// Restart work from the new fiber.
|
||||||
return newWorkInProgress;
|
return newWorkInProgress;
|
||||||
|
|||||||
@@ -5312,9 +5312,11 @@ function doubleInvokeEffectsInDEVIfNecessary(
|
|||||||
if (fiber.memoizedState === null) {
|
if (fiber.memoizedState === null) {
|
||||||
// Only consider Offscreen that is visible.
|
// Only consider Offscreen that is visible.
|
||||||
// TODO (Offscreen) Handle manual mode.
|
// TODO (Offscreen) Handle manual mode.
|
||||||
if (isInStrictMode && fiber.flags & Visibility) {
|
if (isInStrictMode && fiber.flags & (Visibility | PlacementDEV)) {
|
||||||
// Double invoke effects on Offscreen's subtree only
|
// Double invoke effects on Offscreen's subtree
|
||||||
// if it is visible and its visibility has changed.
|
// if it is visible and its visibility has changed.
|
||||||
|
// However, we also need to consider newly hydrated Offscreen because their
|
||||||
|
// visibility flags might not have changed.
|
||||||
runWithFiberInDEV(fiber, doubleInvokeEffectsOnFiber, root, fiber);
|
runWithFiberInDEV(fiber, doubleInvokeEffectsOnFiber, root, fiber);
|
||||||
} else if (fiber.subtreeFlags & PlacementDEV) {
|
} else if (fiber.subtreeFlags & PlacementDEV) {
|
||||||
// Something in the subtree could have been suspended.
|
// Something in the subtree could have been suspended.
|
||||||
|
|||||||
@@ -230,4 +230,127 @@ describe('Activity StrictMode', () => {
|
|||||||
'Child mount',
|
'Child mount',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @gate __DEV__
|
||||||
|
it('should double invoke effects on newly inserted children while Activity becomes visible', async () => {
|
||||||
|
function Parent({children}) {
|
||||||
|
log.push('Parent rendered');
|
||||||
|
React.useEffect(() => {
|
||||||
|
log.push('Parent mount');
|
||||||
|
return () => {
|
||||||
|
log.push('Parent unmount');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Child({name}) {
|
||||||
|
log.push(`Child ${name} rendered`);
|
||||||
|
React.useEffect(() => {
|
||||||
|
log.push(`Child ${name} mount`);
|
||||||
|
return () => {
|
||||||
|
log.push(`Child ${name} unmount`);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Activity mode="hidden">
|
||||||
|
<Parent />
|
||||||
|
</Activity>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(log).toEqual(['Parent rendered', 'Parent rendered']);
|
||||||
|
|
||||||
|
log.length = 0;
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Activity mode="visible">
|
||||||
|
<Parent>
|
||||||
|
<Child name="one" />
|
||||||
|
</Parent>
|
||||||
|
</Activity>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(log).toEqual([
|
||||||
|
'Parent rendered',
|
||||||
|
'Parent rendered',
|
||||||
|
'Child one rendered',
|
||||||
|
'Child one rendered',
|
||||||
|
'Child one mount',
|
||||||
|
'Parent mount',
|
||||||
|
// StrictMode double invocation
|
||||||
|
'Parent unmount',
|
||||||
|
'Child one unmount',
|
||||||
|
'Child one mount',
|
||||||
|
'Parent mount',
|
||||||
|
]);
|
||||||
|
|
||||||
|
log.length = 0;
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Activity mode="visible">
|
||||||
|
<Parent>
|
||||||
|
<Child name="one" />
|
||||||
|
</Parent>
|
||||||
|
</Activity>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(log).toEqual([
|
||||||
|
'Parent rendered',
|
||||||
|
'Parent rendered',
|
||||||
|
'Child one rendered',
|
||||||
|
'Child one rendered',
|
||||||
|
// single Effect invocation. No double invocation on update.
|
||||||
|
'Child one unmount',
|
||||||
|
'Parent unmount',
|
||||||
|
'Child one mount',
|
||||||
|
'Parent mount',
|
||||||
|
]);
|
||||||
|
|
||||||
|
log.length = 0;
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Activity mode="visible">
|
||||||
|
<Parent>
|
||||||
|
<Child name="one" />
|
||||||
|
<Child name="two" />
|
||||||
|
</Parent>
|
||||||
|
</Activity>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(log).toEqual([
|
||||||
|
'Parent rendered',
|
||||||
|
'Parent rendered',
|
||||||
|
'Child one rendered',
|
||||||
|
'Child one rendered',
|
||||||
|
'Child two rendered',
|
||||||
|
'Child two rendered',
|
||||||
|
// single Effect invocation for existing Components.
|
||||||
|
'Child one unmount',
|
||||||
|
'Parent unmount',
|
||||||
|
'Child one mount',
|
||||||
|
'Child two mount',
|
||||||
|
'Parent mount',
|
||||||
|
// Double Effect invocation for new Component "two"
|
||||||
|
'Child two unmount',
|
||||||
|
'Child two mount',
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2326,6 +2326,98 @@ describe('ReactFresh', () => {
|
|||||||
expect(finalEl.style.color).toBe('orange');
|
expect(finalEl.style.color).toBe('orange');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('double invokes effects after a forced remount in StrictMode', async () => {
|
||||||
|
if (__DEV__) {
|
||||||
|
const log = [];
|
||||||
|
|
||||||
|
const createAppV1 = () => {
|
||||||
|
function Hello() {
|
||||||
|
React.useEffect(() => {
|
||||||
|
log.push('mount v1');
|
||||||
|
return () => log.push('unmount v1');
|
||||||
|
}, []);
|
||||||
|
return <p style={{color: 'blue'}}>Hello</p>;
|
||||||
|
}
|
||||||
|
$RefreshReg$(Hello, 'Hello');
|
||||||
|
$RefreshSig$(Hello, '1');
|
||||||
|
|
||||||
|
return Hello;
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = createAppV1();
|
||||||
|
|
||||||
|
await act(() => {
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(log).toEqual(['mount v1', 'unmount v1', 'mount v1']);
|
||||||
|
log.length = 0;
|
||||||
|
|
||||||
|
await patch(() => {
|
||||||
|
function Hello() {
|
||||||
|
React.useEffect(() => {
|
||||||
|
log.push('mount v2');
|
||||||
|
return () => log.push('unmount v2');
|
||||||
|
}, []);
|
||||||
|
return <p style={{color: 'red'}}>Hello</p>;
|
||||||
|
}
|
||||||
|
$RefreshReg$(Hello, 'Hello');
|
||||||
|
$RefreshSig$(Hello, '2');
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.firstChild.style.color).toBe('red');
|
||||||
|
expect(log).toEqual(['unmount v1', 'mount v2', 'unmount v2', 'mount v2']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('double invokes an effect added during Fast Refresh remount in StrictMode', async () => {
|
||||||
|
if (__DEV__) {
|
||||||
|
const log = [];
|
||||||
|
|
||||||
|
const createAppV1 = () => {
|
||||||
|
function Hello() {
|
||||||
|
return <p style={{color: 'blue'}}>Hello</p>;
|
||||||
|
}
|
||||||
|
$RefreshReg$(Hello, 'Hello');
|
||||||
|
$RefreshSig$(Hello, '1');
|
||||||
|
return Hello;
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = createAppV1();
|
||||||
|
|
||||||
|
await act(() => {
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(log).toEqual([]);
|
||||||
|
|
||||||
|
await patch(() => {
|
||||||
|
function Hello() {
|
||||||
|
React.useEffect(() => {
|
||||||
|
log.push('mount v2');
|
||||||
|
return () => log.push('unmount v2');
|
||||||
|
}, []);
|
||||||
|
return <p style={{color: 'red'}}>Hello</p>;
|
||||||
|
}
|
||||||
|
$RefreshReg$(Hello, 'Hello');
|
||||||
|
$RefreshSig$(Hello, '2');
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.firstChild.style.color).toBe('red');
|
||||||
|
expect(log).toEqual(['mount v2', 'unmount v2', 'mount v2']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('resets hooks with dependencies on hot reload', async () => {
|
it('resets hooks with dependencies on hot reload', async () => {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
let useEffectWithEmptyArrayCalls = 0;
|
let useEffectWithEmptyArrayCalls = 0;
|
||||||
|
|||||||
@@ -568,5 +568,19 @@
|
|||||||
"580": "Server Function has too many bound arguments. Received %s but the limit is %s.",
|
"580": "Server Function has too many bound arguments. Received %s but the limit is %s.",
|
||||||
"581": "BigInt is too large. Received %s digits but the limit is %s.",
|
"581": "BigInt is too large. Received %s digits but the limit is %s.",
|
||||||
"582": "Referenced Blob is not a Blob.",
|
"582": "Referenced Blob is not a Blob.",
|
||||||
"583": "The current renderer does not support view transitions. This error is likely caused by a bug in React. Please file an issue."
|
"583": "The current renderer does not support view transitions. This error is likely caused by a bug in React. Please file an issue.",
|
||||||
}
|
"584": "Attempted to load a Server Reference outside the hosted root.",
|
||||||
|
"585": "Invalid server action: %s",
|
||||||
|
"586": "No server callback has been registered. Call setServerCallback to register one.",
|
||||||
|
"587": "Server actions must be functions",
|
||||||
|
"588": "Could not find the module \"%s\" in the React Server Consumer Manifest. This is probably a bug in the React Server Components bundler.",
|
||||||
|
"589": "Could not find the module \"%s\" in the React Server Manifest. This is probably a bug in the React Server Components bundler.",
|
||||||
|
"590": "Cannot await or return from a thenable. You cannot await a client module from a server component.",
|
||||||
|
"591": "Cannot access %s on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.",
|
||||||
|
"592": "Cannot assign to a client module from a server module.",
|
||||||
|
"593": "Attempted to call the default export of %s from the server but it's on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.",
|
||||||
|
"594": "Cannot read Symbol exports. Only named exports are supported on a client module imported on the server.",
|
||||||
|
"595": "Attempted to call %s() from the server but %s is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.",
|
||||||
|
"596": "Could not find the module \"%s\" in the React Client Manifest. This is probably a bug in the React Server Components bundler.",
|
||||||
|
"597": "The module \"%s\" is marked as an async ESM module but was loaded as a CJS proxy. This is probably a bug in the React Server Components bundler."
|
||||||
|
}
|
||||||
@@ -415,7 +415,7 @@ const bundles = [
|
|||||||
entry: 'react-dom/unstable_server-external-runtime',
|
entry: 'react-dom/unstable_server-external-runtime',
|
||||||
outputPath: 'unstable_server-external-runtime.js',
|
outputPath: 'unstable_server-external-runtime.js',
|
||||||
global: 'ReactDOMServerExternalRuntime',
|
global: 'ReactDOMServerExternalRuntime',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: [],
|
externals: [],
|
||||||
},
|
},
|
||||||
@@ -454,7 +454,7 @@ const bundles = [
|
|||||||
name: 'react-server-dom-webpack-server.browser',
|
name: 'react-server-dom-webpack-server.browser',
|
||||||
condition: 'react-server',
|
condition: 'react-server',
|
||||||
global: 'ReactServerDOMServer',
|
global: 'ReactServerDOMServer',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: ['react', 'react-dom'],
|
externals: ['react', 'react-dom'],
|
||||||
},
|
},
|
||||||
@@ -496,7 +496,7 @@ const bundles = [
|
|||||||
'react-server-dom-webpack/src/client/react-flight-dom-client.browser',
|
'react-server-dom-webpack/src/client/react-flight-dom-client.browser',
|
||||||
name: 'react-server-dom-webpack-client.browser',
|
name: 'react-server-dom-webpack-client.browser',
|
||||||
global: 'ReactServerDOMClient',
|
global: 'ReactServerDOMClient',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: ['react', 'react-dom'],
|
externals: ['react', 'react-dom'],
|
||||||
},
|
},
|
||||||
@@ -566,7 +566,7 @@ const bundles = [
|
|||||||
name: 'react-server-dom-turbopack-server.browser',
|
name: 'react-server-dom-turbopack-server.browser',
|
||||||
condition: 'react-server',
|
condition: 'react-server',
|
||||||
global: 'ReactServerDOMServer',
|
global: 'ReactServerDOMServer',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: ['react', 'react-dom'],
|
externals: ['react', 'react-dom'],
|
||||||
},
|
},
|
||||||
@@ -608,7 +608,7 @@ const bundles = [
|
|||||||
'react-server-dom-turbopack/src/client/react-flight-dom-client.browser',
|
'react-server-dom-turbopack/src/client/react-flight-dom-client.browser',
|
||||||
name: 'react-server-dom-turbopack-client.browser',
|
name: 'react-server-dom-turbopack-client.browser',
|
||||||
global: 'ReactServerDOMClient',
|
global: 'ReactServerDOMClient',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: ['react', 'react-dom'],
|
externals: ['react', 'react-dom'],
|
||||||
},
|
},
|
||||||
@@ -641,7 +641,7 @@ const bundles = [
|
|||||||
name: 'react-server-dom-parcel-server.browser',
|
name: 'react-server-dom-parcel-server.browser',
|
||||||
condition: 'react-server',
|
condition: 'react-server',
|
||||||
global: 'ReactServerDOMServer',
|
global: 'ReactServerDOMServer',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: ['react', 'react-dom'],
|
externals: ['react', 'react-dom'],
|
||||||
},
|
},
|
||||||
@@ -682,7 +682,7 @@ const bundles = [
|
|||||||
entry: 'react-server-dom-parcel/src/client/react-flight-dom-client.browser',
|
entry: 'react-server-dom-parcel/src/client/react-flight-dom-client.browser',
|
||||||
name: 'react-server-dom-parcel-client.browser',
|
name: 'react-server-dom-parcel-client.browser',
|
||||||
global: 'ReactServerDOMClient',
|
global: 'ReactServerDOMClient',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: ['react', 'react-dom'],
|
externals: ['react', 'react-dom'],
|
||||||
},
|
},
|
||||||
@@ -731,7 +731,7 @@ const bundles = [
|
|||||||
bundleTypes: [NODE_DEV, NODE_PROD, ESM_DEV, ESM_PROD],
|
bundleTypes: [NODE_DEV, NODE_PROD, ESM_DEV, ESM_PROD],
|
||||||
moduleType: RENDERER,
|
moduleType: RENDERER,
|
||||||
entry: 'react-server-dom-esm/client.browser',
|
entry: 'react-server-dom-esm/client.browser',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: true,
|
||||||
wrapWithModuleBoundaries: false,
|
wrapWithModuleBoundaries: false,
|
||||||
externals: ['react', 'react-dom'],
|
externals: ['react', 'react-dom'],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user