mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge ea28f29199 into sapling-pr-archive-poteto
This commit is contained in:
@@ -549,8 +549,16 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
[
|
||||
'map',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
/**
|
||||
* Note `map`'s arguments are annotated as Effect.ConditionallyMutate as
|
||||
* calling `<array>.map(fn)` might invoke `fn`, which means replaying its
|
||||
* effects.
|
||||
*
|
||||
* (Note that Effect.Read / Effect.Capture on a function type means
|
||||
* potential data dependency or aliasing respectively.)
|
||||
*/
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
@@ -561,7 +569,7 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
'flatMap',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
@@ -572,7 +580,7 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
'filter',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
|
||||
@@ -41,11 +41,16 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect {
|
||||
if (isRefOrRefValue(place.identifier)) {
|
||||
break;
|
||||
} else if (value.kind === ValueKind.Context) {
|
||||
CompilerError.invariant(value.context.size > 0, {
|
||||
reason:
|
||||
"[InferFunctionEffects] Expected Context-kind value's capture list to be non-empty.",
|
||||
loc: place.loc,
|
||||
});
|
||||
return {
|
||||
kind: 'ContextMutation',
|
||||
loc: place.loc,
|
||||
effect: place.effect,
|
||||
places: value.context.size === 0 ? new Set([place]) : value.context,
|
||||
places: value.context,
|
||||
};
|
||||
} else if (
|
||||
value.kind !== ValueKind.Mutable &&
|
||||
|
||||
+43
-32
@@ -857,17 +857,19 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'ArrayExpression': {
|
||||
const valueKind: AbstractValue = hasContextRefOperand(state, instrValue)
|
||||
? {
|
||||
kind: ValueKind.Context,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
const contextRefOperands = getContextRefOperand(state, instrValue);
|
||||
const valueKind: AbstractValue =
|
||||
contextRefOperands.length > 0
|
||||
? {
|
||||
kind: ValueKind.Context,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(contextRefOperands),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind,
|
||||
@@ -918,17 +920,19 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'ObjectExpression': {
|
||||
const valueKind: AbstractValue = hasContextRefOperand(state, instrValue)
|
||||
? {
|
||||
kind: ValueKind.Context,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
const contextRefOperands = getContextRefOperand(state, instrValue);
|
||||
const valueKind: AbstractValue =
|
||||
contextRefOperands.length > 0
|
||||
? {
|
||||
kind: ValueKind.Context,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(contextRefOperands),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
|
||||
for (const property of instrValue.properties) {
|
||||
switch (property.kind) {
|
||||
@@ -1593,15 +1597,21 @@ function inferBlock(
|
||||
}
|
||||
case 'LoadLocal': {
|
||||
const lvalue = instr.lvalue;
|
||||
const effect =
|
||||
state.isDefined(lvalue) &&
|
||||
state.kind(lvalue).kind === ValueKind.Context
|
||||
? Effect.ConditionallyMutate
|
||||
: Effect.Capture;
|
||||
CompilerError.invariant(
|
||||
!(
|
||||
state.isDefined(lvalue) &&
|
||||
state.kind(lvalue).kind === ValueKind.Context
|
||||
),
|
||||
{
|
||||
reason:
|
||||
'[InferReferenceEffects] Unexpected LoadLocal with context kind',
|
||||
loc: lvalue.loc,
|
||||
},
|
||||
);
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.place,
|
||||
effect,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
@@ -1932,19 +1942,20 @@ function inferBlock(
|
||||
);
|
||||
}
|
||||
|
||||
function hasContextRefOperand(
|
||||
function getContextRefOperand(
|
||||
state: InferenceState,
|
||||
instrValue: InstructionValue,
|
||||
): boolean {
|
||||
): Array<Place> {
|
||||
const result = [];
|
||||
for (const place of eachInstructionValueOperand(instrValue)) {
|
||||
if (
|
||||
state.isDefined(place) &&
|
||||
state.kind(place).kind === ValueKind.Context
|
||||
) {
|
||||
return true;
|
||||
result.push(place);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getFunctionCallSignature(
|
||||
|
||||
+2
-2
@@ -2269,7 +2269,7 @@ function codegenInstructionValue(
|
||||
* https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
|
||||
*/
|
||||
const STRING_REQUIRES_EXPR_CONTAINER_PATTERN =
|
||||
/[\u{0000}-\u{001F}\u{007F}\u{0080}-\u{FFFF}]|"/u;
|
||||
/[\u{0000}-\u{001F}\u{007F}\u{0080}-\u{FFFF}]|"|\\/u;
|
||||
function codegenJsxAttribute(
|
||||
cx: Context,
|
||||
attribute: JsxAttribute,
|
||||
@@ -2327,7 +2327,7 @@ function codegenJsxAttribute(
|
||||
}
|
||||
}
|
||||
|
||||
const JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN = /[<>&]/;
|
||||
const JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN = /[<>&{}]/;
|
||||
function codegenJsxElement(
|
||||
cx: Context,
|
||||
place: Place,
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Test() {
|
||||
return (
|
||||
<div>
|
||||
If the string contains the string {pageNumber} it will be
|
||||
replaced by the page number.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Test() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = (
|
||||
<div>
|
||||
{
|
||||
"If the string contains the string {pageNumber} it will be replaced by the page number."
|
||||
}
|
||||
</div>
|
||||
);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>If the string contains the string {pageNumber} it will be replaced by the page number.</div>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
function Test() {
|
||||
return (
|
||||
<div>
|
||||
If the string contains the string {pageNumber} it will be
|
||||
replaced by the page number.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test,
|
||||
params: [],
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Fixture showing `@babel/generator` bug with jsx attribute strings containing
|
||||
* escape sequences. Note that this is only a problem when generating jsx
|
||||
* literals.
|
||||
*
|
||||
* When using the jsx transform to correctly lower jsx into
|
||||
* `React.createElement` calls, the escape sequences are preserved correctly
|
||||
* (see evaluator output).
|
||||
*/
|
||||
function MyApp() {
|
||||
return <input pattern="\w" />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; /**
|
||||
* Fixture showing `@babel/generator` bug with jsx attribute strings containing
|
||||
* escape sequences. Note that this is only a problem when generating jsx
|
||||
* literals.
|
||||
*
|
||||
* When using the jsx transform to correctly lower jsx into
|
||||
* `React.createElement` calls, the escape sequences are preserved correctly
|
||||
* (see evaluator output).
|
||||
*/
|
||||
function MyApp() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <input pattern={"\\w"} />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <input pattern="\w">
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Fixture showing `@babel/generator` bug with jsx attribute strings containing
|
||||
* escape sequences. Note that this is only a problem when generating jsx
|
||||
* literals.
|
||||
*
|
||||
* When using the jsx transform to correctly lower jsx into
|
||||
* `React.createElement` calls, the escape sequences are preserved correctly
|
||||
* (see evaluator output).
|
||||
*/
|
||||
function MyApp() {
|
||||
return <input pattern="\w" />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: MyApp,
|
||||
params: [],
|
||||
};
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {
|
||||
arrayPush,
|
||||
identity,
|
||||
makeArray,
|
||||
Stringify,
|
||||
useFragment,
|
||||
} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug repro showing why it's invalid for function references to be annotated
|
||||
* with a `Read` effect when that reference might lead to the function being
|
||||
* invoked.
|
||||
*
|
||||
* Note that currently, `Array.map` is annotated to have `Read` effects on its
|
||||
* operands. This is incorrect as function effects must be replayed when `map`
|
||||
* is called
|
||||
* - Read: non-aliasing data dependency
|
||||
* - Capture: maybe-aliasing data dependency
|
||||
* - ConditionallyMutate: maybe-aliasing data dependency; maybe-write / invoke
|
||||
* but only if the value is mutable
|
||||
*
|
||||
* Invalid evaluator result: Found differences in evaluator results Non-forget
|
||||
* (expected): (kind: ok)
|
||||
* <div>{"x":[2,2,2],"count":3}</div><div>{"item":1}</div>
|
||||
* <div>{"x":[2,2,2],"count":4}</div><div>{"item":1}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"x":[2,2,2],"count":3}</div><div>{"item":1}</div>
|
||||
* <div>{"x":[2,2,2,2,2,2],"count":4}</div><div>{"item":1}</div>
|
||||
*/
|
||||
|
||||
function Component({extraJsx}) {
|
||||
const x = makeArray();
|
||||
const items = useFragment();
|
||||
// This closure has the following effects that must be replayed:
|
||||
// - MaybeFreeze / Capture of `items`
|
||||
// - ConditionalMutate of x
|
||||
const jsx = items.a.map((item, i) => {
|
||||
arrayPush(x, 2);
|
||||
return <Stringify item={item} key={i} />;
|
||||
});
|
||||
const offset = jsx.length;
|
||||
for (let i = 0; i < extraJsx; i++) {
|
||||
jsx.push(<Stringify item={0} key={i + offset} />);
|
||||
}
|
||||
const count = jsx.length;
|
||||
identity(count);
|
||||
return (
|
||||
<>
|
||||
<Stringify x={x} count={count} />
|
||||
{jsx[0]}
|
||||
</>
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{extraJsx: 0}],
|
||||
sequentialRenders: [{extraJsx: 0}, {extraJsx: 1}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import {
|
||||
arrayPush,
|
||||
identity,
|
||||
makeArray,
|
||||
Stringify,
|
||||
useFragment,
|
||||
} from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Bug repro showing why it's invalid for function references to be annotated
|
||||
* with a `Read` effect when that reference might lead to the function being
|
||||
* invoked.
|
||||
*
|
||||
* Note that currently, `Array.map` is annotated to have `Read` effects on its
|
||||
* operands. This is incorrect as function effects must be replayed when `map`
|
||||
* is called
|
||||
* - Read: non-aliasing data dependency
|
||||
* - Capture: maybe-aliasing data dependency
|
||||
* - ConditionallyMutate: maybe-aliasing data dependency; maybe-write / invoke
|
||||
* but only if the value is mutable
|
||||
*
|
||||
* Invalid evaluator result: Found differences in evaluator results Non-forget
|
||||
* (expected): (kind: ok)
|
||||
* <div>{"x":[2,2,2],"count":3}</div><div>{"item":1}</div>
|
||||
* <div>{"x":[2,2,2],"count":4}</div><div>{"item":1}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"x":[2,2,2],"count":3}</div><div>{"item":1}</div>
|
||||
* <div>{"x":[2,2,2,2,2,2],"count":4}</div><div>{"item":1}</div>
|
||||
*/
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(6);
|
||||
const { extraJsx } = t0;
|
||||
const x = makeArray();
|
||||
const items = useFragment();
|
||||
|
||||
const jsx = items.a.map((item, i) => {
|
||||
arrayPush(x, 2);
|
||||
return <Stringify item={item} key={i} />;
|
||||
});
|
||||
const offset = jsx.length;
|
||||
for (let i_0 = 0; i_0 < extraJsx; i_0++) {
|
||||
jsx.push(<Stringify item={0} key={i_0 + offset} />);
|
||||
}
|
||||
|
||||
const count = jsx.length;
|
||||
identity(count);
|
||||
let t1;
|
||||
if ($[0] !== count || $[1] !== x) {
|
||||
t1 = <Stringify x={x} count={count} />;
|
||||
$[0] = count;
|
||||
$[1] = x;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
const t2 = jsx[0];
|
||||
let t3;
|
||||
if ($[3] !== t1 || $[4] !== t2) {
|
||||
t3 = (
|
||||
<>
|
||||
{t1}
|
||||
{t2}
|
||||
</>
|
||||
);
|
||||
$[3] = t1;
|
||||
$[4] = t2;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ extraJsx: 0 }],
|
||||
sequentialRenders: [{ extraJsx: 0 }, { extraJsx: 1 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"x":[2,2,2],"count":3}</div><div>{"item":1}</div>
|
||||
<div>{"x":[2,2,2],"count":4}</div><div>{"item":1}</div>
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
arrayPush,
|
||||
identity,
|
||||
makeArray,
|
||||
Stringify,
|
||||
useFragment,
|
||||
} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug repro showing why it's invalid for function references to be annotated
|
||||
* with a `Read` effect when that reference might lead to the function being
|
||||
* invoked.
|
||||
*
|
||||
* Note that currently, `Array.map` is annotated to have `Read` effects on its
|
||||
* operands. This is incorrect as function effects must be replayed when `map`
|
||||
* is called
|
||||
* - Read: non-aliasing data dependency
|
||||
* - Capture: maybe-aliasing data dependency
|
||||
* - ConditionallyMutate: maybe-aliasing data dependency; maybe-write / invoke
|
||||
* but only if the value is mutable
|
||||
*
|
||||
* Invalid evaluator result: Found differences in evaluator results Non-forget
|
||||
* (expected): (kind: ok)
|
||||
* <div>{"x":[2,2,2],"count":3}</div><div>{"item":1}</div>
|
||||
* <div>{"x":[2,2,2],"count":4}</div><div>{"item":1}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"x":[2,2,2],"count":3}</div><div>{"item":1}</div>
|
||||
* <div>{"x":[2,2,2,2,2,2],"count":4}</div><div>{"item":1}</div>
|
||||
*/
|
||||
|
||||
function Component({extraJsx}) {
|
||||
const x = makeArray();
|
||||
const items = useFragment();
|
||||
// This closure has the following effects that must be replayed:
|
||||
// - MaybeFreeze / Capture of `items`
|
||||
// - ConditionalMutate of x
|
||||
const jsx = items.a.map((item, i) => {
|
||||
arrayPush(x, 2);
|
||||
return <Stringify item={item} key={i} />;
|
||||
});
|
||||
const offset = jsx.length;
|
||||
for (let i = 0; i < extraJsx; i++) {
|
||||
jsx.push(<Stringify item={0} key={i + offset} />);
|
||||
}
|
||||
const count = jsx.length;
|
||||
identity(count);
|
||||
return (
|
||||
<>
|
||||
<Stringify x={x} count={count} />
|
||||
{jsx[0]}
|
||||
</>
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{extraJsx: 0}],
|
||||
sequentialRenders: [{extraJsx: 0}, {extraJsx: 1}],
|
||||
};
|
||||
+11
-3
@@ -42,15 +42,23 @@ function V0(t0) {
|
||||
<ComponentC cd="TxqUy" ce="oh`]uc" cf="Bdbo" c10={!V9.va && v11.v12}>
|
||||
gmhubcw
|
||||
{v1 === V3.V13 ? (
|
||||
<c14 c15="L^]w\\\\T\\\\qrGmqrlQyrvBgf\\\\inuRdkEqwVPwixiriYGSZmKJf]E]RdT{N[WyVPiEJIbdFzvDohJV[BV`H[[K^xoy[HOGKDqVzUJ^h">
|
||||
<c14
|
||||
c15={
|
||||
"L^]w\\\\T\\\\qrGmqrlQyrvBgf\\\\inuRdkEqwVPwixiriYGSZmKJf]E]RdT{N[WyVPiEJIbdFzvDohJV[BV`H[[K^xoy[HOGKDqVzUJ^h"
|
||||
}
|
||||
>
|
||||
iawyneijcgamsfgrrjyvhjrrqvzexxwenxqoknnilmfloafyvnvkqbssqnxnexqvtcpvjysaiovjxyqrorqskfph
|
||||
</c14>
|
||||
) : v16.v17("pyorztRC]EJzVuP^e") ? (
|
||||
<c14 c15="CRinMqvmOknWRAKERI]RBzB_LXGKQe{SUpoN[\\\\gL[`bLMOhvFqDVVMNOdY">
|
||||
<c14
|
||||
c15={
|
||||
"CRinMqvmOknWRAKERI]RBzB_LXGKQe{SUpoN[\\\\gL[`bLMOhvFqDVVMNOdY"
|
||||
}
|
||||
>
|
||||
goprinbjmmjhfserfuqyluxcewpyjihektogc
|
||||
</c14>
|
||||
) : (
|
||||
<c14 c15="H\\\\\\\\GAcTc\\\\lfGMW[yHriCpvW`w]niSIKj\\\\kdgFI">
|
||||
<c14 c15={"H\\\\\\\\GAcTc\\\\lfGMW[yHriCpvW`w]niSIKj\\\\kdgFI"}>
|
||||
yejarlvudihqdrdgpvahovggdnmgnueedxpbwbkdvvkdhqwrtoiual
|
||||
</c14>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,24 @@ npm install eslint-plugin-react-compiler --save-dev
|
||||
|
||||
## Usage
|
||||
|
||||
Add `react-compiler` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
|
||||
### Flat config
|
||||
|
||||
Edit your eslint 8+ config (for example `eslint.config.mjs`) with the recommended configuration:
|
||||
|
||||
```diff
|
||||
+ import reactCompiler from "eslint-plugin-react-compiler"
|
||||
import react from "eslint-plugin-react"
|
||||
|
||||
export default [
|
||||
// Your existing config
|
||||
{ ...pluginReact.configs.flat.recommended, settings: { react: { version: "detect" } } },
|
||||
+ reactCompiler.configs.recommended
|
||||
]
|
||||
```
|
||||
|
||||
### Legacy config (`.eslintrc`)
|
||||
|
||||
Add `react-compiler` to the plugins section of your configuration file. You can omit the `eslint-plugin-` prefix:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -11,4 +11,18 @@ module.exports = {
|
||||
rules: {
|
||||
'react-compiler': ReactCompilerRule,
|
||||
},
|
||||
configs: {
|
||||
recommended: {
|
||||
plugins: {
|
||||
'react-compiler': {
|
||||
rules: {
|
||||
'react-compiler': ReactCompilerRule,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'react-compiler/react-compiler': 'error',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -486,6 +486,7 @@ const skipFilter = new Set([
|
||||
'bug-aliased-capture-mutate',
|
||||
'bug-functiondecl-hoisting',
|
||||
'bug-try-catch-maybe-null-dependency',
|
||||
'bug-invalid-mixedreadonly-map-shape',
|
||||
'bug-type-inference-control-flow',
|
||||
'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted',
|
||||
'bug-invalid-phi-as-dependency',
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, {
|
||||
useLayoutEffect,
|
||||
useEffect,
|
||||
useState,
|
||||
unstable_addTransitionType as addTransitionType,
|
||||
} from 'react';
|
||||
|
||||
import Chrome from './Chrome';
|
||||
@@ -35,11 +36,23 @@ export default function App({assets, initialURL}) {
|
||||
if (!event.canIntercept) {
|
||||
return;
|
||||
}
|
||||
const navigationType = event.navigationType;
|
||||
const previousIndex = window.navigation.currentEntry.index;
|
||||
const newURL = new URL(event.destination.url);
|
||||
event.intercept({
|
||||
handler() {
|
||||
let promise;
|
||||
startTransition(() => {
|
||||
addTransitionType('navigation-' + navigationType);
|
||||
if (navigationType === 'traverse') {
|
||||
// For traverse types it's useful to distinguish going back or forward.
|
||||
const nextIndex = event.destination.index;
|
||||
if (nextIndex > previousIndex) {
|
||||
addTransitionType('navigation-forward');
|
||||
} else if (nextIndex < previousIndex) {
|
||||
addTransitionType('navigation-back');
|
||||
}
|
||||
}
|
||||
promise = new Promise(resolve => {
|
||||
setRouterState({
|
||||
url: newURL.pathname + newURL.search,
|
||||
|
||||
@@ -36,7 +36,7 @@ function Component() {
|
||||
|
||||
export default function Page({url, navigate}) {
|
||||
const show = url === '/?b';
|
||||
function onTransition(viewTransition) {
|
||||
function onTransition(viewTransition, types) {
|
||||
const keyframes = [
|
||||
{rotate: '0deg', transformOrigin: '30px 8px'},
|
||||
{rotate: '360deg', transformOrigin: '30px 8px'},
|
||||
@@ -59,6 +59,16 @@ export default function Page({url, navigate}) {
|
||||
</button>
|
||||
<ViewTransition className="none">
|
||||
<div>
|
||||
<ViewTransition className={transitions['slide-on-nav']}>
|
||||
<h1>{!show ? 'A' : 'B'}</h1>
|
||||
</ViewTransition>
|
||||
<ViewTransition
|
||||
className={{
|
||||
'navigation-back': transitions['slide-right'],
|
||||
'navigation-forward': transitions['slide-left'],
|
||||
}}>
|
||||
<h1>{!show ? 'A' : 'B'}</h1>
|
||||
</ViewTransition>
|
||||
{show ? (
|
||||
<div>
|
||||
{a}
|
||||
|
||||
@@ -9,7 +9,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes exit-slide-left {
|
||||
@keyframes enter-slide-left {
|
||||
0% {
|
||||
opacity: 0;
|
||||
translate: 200px 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
translate: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes exit-slide-right {
|
||||
0% {
|
||||
opacity: 1;
|
||||
translate: 0 0;
|
||||
@@ -20,9 +31,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes exit-slide-left {
|
||||
0% {
|
||||
opacity: 1;
|
||||
translate: 0 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
translate: -200px 0;
|
||||
}
|
||||
}
|
||||
|
||||
::view-transition-new(.slide-right) {
|
||||
animation: enter-slide-right ease-in 0.25s;
|
||||
}
|
||||
::view-transition-old(.slide-right) {
|
||||
animation: exit-slide-right ease-in 0.25s;
|
||||
}
|
||||
::view-transition-new(.slide-left) {
|
||||
animation: enter-slide-left ease-in 0.25s;
|
||||
}
|
||||
::view-transition-old(.slide-left) {
|
||||
animation: exit-slide-left ease-in 0.25s;
|
||||
}
|
||||
|
||||
::view-transition-new(.enter-slide-right):only-child {
|
||||
animation: enter-slide-right ease-in 0.25s;
|
||||
}
|
||||
::view-transition-old(.exit-slide-left):only-child {
|
||||
animation: exit-slide-left ease-in 0.25s;
|
||||
}
|
||||
|
||||
:root:active-view-transition-type(navigation-back) {
|
||||
&::view-transition-new(.slide-on-nav) {
|
||||
animation: enter-slide-right ease-in 0.25s;
|
||||
}
|
||||
&::view-transition-old(.slide-on-nav) {
|
||||
animation: exit-slide-right ease-in 0.25s;
|
||||
}
|
||||
}
|
||||
|
||||
:root:active-view-transition-type(navigation-forward) {
|
||||
&::view-transition-new(.slide-on-nav) {
|
||||
animation: enter-slide-left ease-in 0.25s;
|
||||
}
|
||||
&::view-transition-old(.slide-on-nav) {
|
||||
animation: exit-slide-left ease-in 0.25s;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -16,6 +16,7 @@ import type {
|
||||
ReactTimeInfo,
|
||||
ReactStackTrace,
|
||||
ReactCallSite,
|
||||
ReactErrorInfoDev,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {LazyComponent} from 'react/src/ReactLazy';
|
||||
|
||||
@@ -2123,11 +2124,12 @@ function resolveErrorProd(response: Response): Error {
|
||||
|
||||
function resolveErrorDev(
|
||||
response: Response,
|
||||
errorInfo: {message: string, stack: ReactStackTrace, env: string, ...},
|
||||
errorInfo: ReactErrorInfoDev,
|
||||
): Error {
|
||||
const message: string = errorInfo.message;
|
||||
const stack: ReactStackTrace = errorInfo.stack;
|
||||
const env: string = errorInfo.env;
|
||||
const name = errorInfo.name;
|
||||
const message = errorInfo.message;
|
||||
const stack = errorInfo.stack;
|
||||
const env = errorInfo.env;
|
||||
|
||||
if (!__DEV__) {
|
||||
// These errors should never make it into a build so we don't need to encode them in codes.json
|
||||
@@ -2156,6 +2158,7 @@ function resolveErrorDev(
|
||||
error = callStack();
|
||||
}
|
||||
|
||||
(error: any).name = name;
|
||||
(error: any).environmentName = env;
|
||||
return error;
|
||||
}
|
||||
|
||||
+13
-2
@@ -694,9 +694,17 @@ describe('ReactFlight', () => {
|
||||
});
|
||||
|
||||
it('can transport Error objects as values', async () => {
|
||||
class CustomError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'Custom';
|
||||
}
|
||||
}
|
||||
|
||||
function ComponentClient({prop}) {
|
||||
return `
|
||||
is error: ${prop instanceof Error}
|
||||
name: ${prop.name}
|
||||
message: ${prop.message}
|
||||
stack: ${normalizeCodeLocInfo(prop.stack).split('\n').slice(0, 2).join('\n')}
|
||||
environmentName: ${prop.environmentName}
|
||||
@@ -705,7 +713,7 @@ describe('ReactFlight', () => {
|
||||
const Component = clientReference(ComponentClient);
|
||||
|
||||
function ServerComponent() {
|
||||
const error = new Error('hello');
|
||||
const error = new CustomError('hello');
|
||||
return <Component prop={error} />;
|
||||
}
|
||||
|
||||
@@ -718,14 +726,16 @@ describe('ReactFlight', () => {
|
||||
if (__DEV__) {
|
||||
expect(ReactNoop).toMatchRenderedOutput(`
|
||||
is error: true
|
||||
name: Custom
|
||||
message: hello
|
||||
stack: Error: hello
|
||||
stack: Custom: hello
|
||||
in ServerComponent (at **)
|
||||
environmentName: Server
|
||||
`);
|
||||
} else {
|
||||
expect(ReactNoop).toMatchRenderedOutput(`
|
||||
is error: true
|
||||
name: Error
|
||||
message: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.
|
||||
stack: Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.
|
||||
environmentName: undefined
|
||||
@@ -1377,6 +1387,7 @@ describe('ReactFlight', () => {
|
||||
errors: [
|
||||
{
|
||||
message: 'This is an error',
|
||||
name: 'Error',
|
||||
stack: expect.stringContaining(
|
||||
'Error: This is an error\n' +
|
||||
' at eval (eval at testFunction (inspected-page.html:29:11),%20%3Canonymous%3E:1:35)\n' +
|
||||
|
||||
+17
-14
@@ -127,6 +127,10 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
|
||||
}
|
||||
|
||||
Dispatcher.useId();
|
||||
|
||||
if (typeof Dispatcher.useEffectEvent === 'function') {
|
||||
Dispatcher.useEffectEvent((args: empty) => {});
|
||||
}
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
@@ -366,8 +370,11 @@ function useInsertionEffect(
|
||||
}
|
||||
|
||||
function useEffect(
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
nextHook();
|
||||
hookLog.push({
|
||||
@@ -731,22 +738,18 @@ function useHostTransitionStatus(): TransitionStatus {
|
||||
return status;
|
||||
}
|
||||
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
) {
|
||||
function useEffectEvent<Args, F: (...Array<Args>) => mixed>(callback: F): F {
|
||||
nextHook();
|
||||
hookLog.push({
|
||||
displayName: null,
|
||||
primitive: 'ResourceEffect',
|
||||
primitive: 'EffectEvent',
|
||||
stackError: new Error(),
|
||||
value: create,
|
||||
value: callback,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'ResourceEffect',
|
||||
dispatcherHookName: 'EffectEvent',
|
||||
});
|
||||
|
||||
return callback;
|
||||
}
|
||||
|
||||
const Dispatcher: DispatcherType = {
|
||||
@@ -773,7 +776,7 @@ const Dispatcher: DispatcherType = {
|
||||
useFormState,
|
||||
useActionState,
|
||||
useHostTransitionStatus,
|
||||
useResourceEffect,
|
||||
useEffectEvent,
|
||||
};
|
||||
|
||||
// create a proxy to throw a custom error
|
||||
@@ -962,7 +965,7 @@ function parseHookName(functionName: void | string): string {
|
||||
startIndex += 'unstable_'.length;
|
||||
}
|
||||
|
||||
if (functionName.slice(startIndex).startsWith('unstable_')) {
|
||||
if (functionName.slice(startIndex).startsWith('experimental_')) {
|
||||
startIndex += 'experimental_'.length;
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ import NestedProps from './NestedProps';
|
||||
import SimpleValues from './SimpleValues';
|
||||
import SymbolKeys from './SymbolKeys';
|
||||
import UseMemoCache from './UseMemoCache';
|
||||
import UseEffectEvent from './UseEffectEvent';
|
||||
|
||||
// TODO Add Immutable JS example
|
||||
|
||||
@@ -36,6 +37,7 @@ export default function InspectableElements(): React.Node {
|
||||
<CircularReferences />
|
||||
<SymbolKeys />
|
||||
<UseMemoCache />
|
||||
<UseEffectEvent />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const {experimental_useEffectEvent, useState, useEffect} = React;
|
||||
|
||||
export default function UseEffectEvent(): React.Node {
|
||||
return (
|
||||
<>
|
||||
<SingleHookCase />
|
||||
<HookTreeCase />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SingleHookCase() {
|
||||
const onClick = experimental_useEffectEvent(() => {});
|
||||
|
||||
return <div onClick={onClick} />;
|
||||
}
|
||||
|
||||
function useCustomHook() {
|
||||
const [state, setState] = useState();
|
||||
const onClick = experimental_useEffectEvent(() => {});
|
||||
useEffect(() => {});
|
||||
|
||||
return [state, setState, onClick];
|
||||
}
|
||||
|
||||
function HookTreeCase() {
|
||||
const onClick = useCustomHook();
|
||||
|
||||
return <div onClick={onClick} />;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import type {
|
||||
PreinitScriptOptions,
|
||||
PreinitModuleScriptOptions,
|
||||
} from 'react-dom/src/shared/ReactDOMTypes';
|
||||
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
|
||||
|
||||
import {NotPending} from '../shared/ReactDOMFormActions';
|
||||
|
||||
@@ -1235,6 +1236,7 @@ const SUSPENSEY_FONT_TIMEOUT = 500;
|
||||
|
||||
export function startViewTransition(
|
||||
rootContainer: Container,
|
||||
transitionTypes: null | TransitionTypes,
|
||||
mutationCallback: () => void,
|
||||
layoutCallback: () => void,
|
||||
afterMutationCallback: () => void,
|
||||
@@ -1293,7 +1295,7 @@ export function startViewTransition(
|
||||
afterMutationCallback();
|
||||
}
|
||||
},
|
||||
types: null, // TODO: Provide types.
|
||||
types: transitionTypes,
|
||||
});
|
||||
// $FlowFixMe[prop-missing]
|
||||
ownerDocument.__reactViewTransition = transition;
|
||||
|
||||
@@ -27,7 +27,6 @@ let useRef;
|
||||
let useImperativeHandle;
|
||||
let useInsertionEffect;
|
||||
let useLayoutEffect;
|
||||
let useResourceEffect;
|
||||
let useDebugValue;
|
||||
let forwardRef;
|
||||
let yieldedValues;
|
||||
@@ -52,7 +51,6 @@ function initModules() {
|
||||
useImperativeHandle = React.useImperativeHandle;
|
||||
useInsertionEffect = React.useInsertionEffect;
|
||||
useLayoutEffect = React.useLayoutEffect;
|
||||
useResourceEffect = React.experimental_useResourceEffect;
|
||||
forwardRef = React.forwardRef;
|
||||
|
||||
yieldedValues = [];
|
||||
@@ -655,15 +653,15 @@ describe('ReactDOMServerHooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('useResourceEffect', () => {
|
||||
describe('useEffect with CRUD overload', () => {
|
||||
gate(flags => {
|
||||
if (flags.enableUseResourceEffectHook) {
|
||||
if (flags.enableUseEffectCRUDOverload) {
|
||||
const yields = [];
|
||||
itRenders(
|
||||
'should ignore resource effects on the server',
|
||||
async render => {
|
||||
function Counter(props) {
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
yieldValue('created on client');
|
||||
return {resource_counter: props.count};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {InspectorData, TouchedViewDataAtPoint} from './ReactNativeTypes';
|
||||
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
|
||||
|
||||
// Modules provided by RN:
|
||||
import {
|
||||
@@ -582,6 +583,7 @@ export function hasInstanceAffectedParent(
|
||||
|
||||
export function startViewTransition(
|
||||
rootContainer: Container,
|
||||
transitionTypes: null | TransitionTypes,
|
||||
mutationCallback: () => void,
|
||||
layoutCallback: () => void,
|
||||
afterMutationCallback: () => void,
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue'
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {RootTag} from 'react-reconciler/src/ReactRootTags';
|
||||
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
|
||||
|
||||
import * as Scheduler from 'scheduler/unstable_mock';
|
||||
import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
|
||||
@@ -780,6 +781,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
|
||||
startViewTransition(
|
||||
rootContainer: Container,
|
||||
transitionTypes: null | TransitionTypes,
|
||||
mutationCallback: () => void,
|
||||
afterMutationCallback: () => void,
|
||||
layoutCallback: () => void,
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
ResourceEffectIdentityKind,
|
||||
ResourceEffectUpdateKind,
|
||||
} from './ReactFiberHooks';
|
||||
import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags';
|
||||
import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags';
|
||||
|
||||
// These indirections exists so we can exclude its stack frame in DEV (and anything below it).
|
||||
// TODO: Consider marking the whole bundle instead of these boundaries.
|
||||
@@ -183,12 +183,12 @@ export const callComponentWillUnmountInDEV: (
|
||||
const callCreate = {
|
||||
'react-stack-bottom-frame': function (
|
||||
effect: Effect,
|
||||
): (() => void) | mixed | void {
|
||||
if (!enableUseResourceEffectHook) {
|
||||
): (() => void) | {...} | void | null {
|
||||
if (!enableUseEffectCRUDOverload) {
|
||||
if (effect.resourceKind != null) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Expected only SimpleEffects when enableUseResourceEffectHook is disabled, ' +
|
||||
'Expected only SimpleEffects when enableUseEffectCRUDOverload is disabled, ' +
|
||||
'got %s',
|
||||
effect.resourceKind,
|
||||
);
|
||||
@@ -254,7 +254,7 @@ const callDestroy = {
|
||||
export const callDestroyInDEV: (
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
destroy: () => void,
|
||||
destroy: (() => void) | (({...}) => void),
|
||||
) => void = __DEV__
|
||||
? // We use this technique to trick minifiers to preserve the function name.
|
||||
(callDestroy['react-stack-bottom-frame'].bind(callDestroy): any)
|
||||
|
||||
+16
-39
@@ -22,7 +22,7 @@ import {
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerNestedUpdatePhase,
|
||||
enableSchedulingProfiler,
|
||||
enableUseResourceEffectHook,
|
||||
enableUseEffectCRUDOverload,
|
||||
enableViewTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
@@ -160,7 +160,7 @@ export function commitHookEffectListMount(
|
||||
|
||||
// Mount
|
||||
let destroy;
|
||||
if (enableUseResourceEffectHook) {
|
||||
if (enableUseEffectCRUDOverload) {
|
||||
if (effect.resourceKind === ResourceEffectIdentityKind) {
|
||||
if (__DEV__) {
|
||||
effect.inst.resource = runWithFiberInDEV(
|
||||
@@ -170,8 +170,9 @@ export function commitHookEffectListMount(
|
||||
);
|
||||
if (effect.inst.resource == null) {
|
||||
console.error(
|
||||
'useResourceEffect must provide a callback which returns a resource. ' +
|
||||
'If a managed resource is not needed here, use useEffect. Received %s',
|
||||
'useEffect must provide a callback which returns a resource. ' +
|
||||
'If a managed resource is not needed here, do not provide an updater or ' +
|
||||
'destroy callback. Received %s',
|
||||
effect.inst.resource,
|
||||
);
|
||||
}
|
||||
@@ -200,7 +201,7 @@ export function commitHookEffectListMount(
|
||||
if ((flags & HookInsertion) !== NoHookEffect) {
|
||||
setIsRunningInsertionEffect(true);
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
if (enableUseEffectCRUDOverload) {
|
||||
if (effect.resourceKind == null) {
|
||||
destroy = runWithFiberInDEV(
|
||||
finishedWork,
|
||||
@@ -219,7 +220,7 @@ export function commitHookEffectListMount(
|
||||
setIsRunningInsertionEffect(false);
|
||||
}
|
||||
} else {
|
||||
if (enableUseResourceEffectHook) {
|
||||
if (enableUseEffectCRUDOverload) {
|
||||
if (effect.resourceKind == null) {
|
||||
const create = effect.create;
|
||||
const inst = effect.inst;
|
||||
@@ -230,7 +231,7 @@ export function commitHookEffectListMount(
|
||||
if (effect.resourceKind != null) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Expected only SimpleEffects when enableUseResourceEffectHook is disabled, ' +
|
||||
'Expected only SimpleEffects when enableUseEffectCRUDOverload is disabled, ' +
|
||||
'got %s',
|
||||
effect.resourceKind,
|
||||
);
|
||||
@@ -261,11 +262,6 @@ export function commitHookEffectListMount(
|
||||
hookName = 'useLayoutEffect';
|
||||
} else if ((effect.tag & HookInsertion) !== NoFlags) {
|
||||
hookName = 'useInsertionEffect';
|
||||
} else if (
|
||||
enableUseResourceEffectHook &&
|
||||
effect.resourceKind != null
|
||||
) {
|
||||
hookName = 'useResourceEffect';
|
||||
} else {
|
||||
hookName = 'useEffect';
|
||||
}
|
||||
@@ -274,6 +270,7 @@ export function commitHookEffectListMount(
|
||||
addendum =
|
||||
' You returned null. If your effect does not require clean ' +
|
||||
'up, return undefined (or nothing).';
|
||||
// $FlowFixMe (@poteto) this check is safe on arbitrary non-null/void objects
|
||||
} else if (typeof destroy.then === 'function') {
|
||||
addendum =
|
||||
'\n\nIt looks like you wrote ' +
|
||||
@@ -337,7 +334,7 @@ export function commitHookEffectListUnmount(
|
||||
const inst = effect.inst;
|
||||
const destroy = inst.destroy;
|
||||
if (destroy !== undefined) {
|
||||
if (enableUseResourceEffectHook) {
|
||||
if (enableUseEffectCRUDOverload) {
|
||||
if (effect.resourceKind == null) {
|
||||
inst.destroy = undefined;
|
||||
}
|
||||
@@ -357,12 +354,12 @@ export function commitHookEffectListUnmount(
|
||||
setIsRunningInsertionEffect(true);
|
||||
}
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
if (enableUseEffectCRUDOverload) {
|
||||
if (
|
||||
effect.resourceKind === ResourceEffectIdentityKind &&
|
||||
effect.inst.resource != null
|
||||
) {
|
||||
safelyCallDestroyWithResource(
|
||||
safelyCallDestroy(
|
||||
finishedWork,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
@@ -1014,31 +1011,10 @@ export function safelyDetachRef(
|
||||
function safelyCallDestroy(
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
destroy: () => void,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
current,
|
||||
callDestroyInDEV,
|
||||
current,
|
||||
nearestMountedAncestor,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
destroy();
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safelyCallDestroyWithResource(
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
destroy: mixed => void,
|
||||
resource: mixed,
|
||||
destroy: (() => void) | (({...}) => void),
|
||||
resource?: {...} | void | null,
|
||||
) {
|
||||
// $FlowFixMe[extra-arg] @poteto this is safe either way because the extra arg is ignored if it's not a CRUD effect
|
||||
const destroy_ = resource == null ? destroy : destroy.bind(null, resource);
|
||||
if (__DEV__) {
|
||||
runWithFiberInDEV(
|
||||
@@ -1050,6 +1026,7 @@ function safelyCallDestroyWithResource(
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
// $FlowFixMe(incompatible-call) Already bound to resource
|
||||
destroy_();
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
||||
|
||||
+237
-231
@@ -38,7 +38,7 @@ import {
|
||||
enableSchedulingProfiler,
|
||||
enableTransitionTracing,
|
||||
enableUseEffectEventHook,
|
||||
enableUseResourceEffectHook,
|
||||
enableUseEffectCRUDOverload,
|
||||
enableLegacyCache,
|
||||
disableLegacyMode,
|
||||
enableNoCloningMemoCache,
|
||||
@@ -205,8 +205,8 @@ export type Hook = {
|
||||
// the additional memory and we can follow up with performance
|
||||
// optimizations later.
|
||||
type EffectInstance = {
|
||||
resource: mixed,
|
||||
destroy: void | (() => void) | ((resource: mixed) => void),
|
||||
resource: {...} | void | null,
|
||||
destroy: void | (() => void) | ((resource: {...} | void | null) => void),
|
||||
};
|
||||
|
||||
export const ResourceEffectIdentityKind: 0 = 0;
|
||||
@@ -229,7 +229,7 @@ export type ResourceEffectIdentity = {
|
||||
resourceKind: typeof ResourceEffectIdentityKind,
|
||||
tag: HookFlags,
|
||||
inst: EffectInstance,
|
||||
create: () => mixed,
|
||||
create: () => {...} | void | null,
|
||||
deps: Array<mixed> | void | null,
|
||||
next: Effect,
|
||||
};
|
||||
@@ -237,7 +237,7 @@ export type ResourceEffectUpdate = {
|
||||
resourceKind: typeof ResourceEffectUpdateKind,
|
||||
tag: HookFlags,
|
||||
inst: EffectInstance,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
update: ((resource: {...} | void | null) => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
next: Effect,
|
||||
identity: ResourceEffectIdentity,
|
||||
@@ -2523,12 +2523,15 @@ function pushSimpleEffect(
|
||||
tag: HookFlags,
|
||||
inst: EffectInstance,
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): Effect {
|
||||
const effect: Effect = {
|
||||
tag,
|
||||
create,
|
||||
deps,
|
||||
deps: createDeps,
|
||||
inst,
|
||||
// Circular
|
||||
next: (null: any),
|
||||
@@ -2540,9 +2543,9 @@ function pushResourceEffect(
|
||||
identityTag: HookFlags,
|
||||
updateTag: HookFlags,
|
||||
inst: EffectInstance,
|
||||
create: () => mixed,
|
||||
create: () => {...} | void | null,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
update: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
): Effect {
|
||||
const effectIdentity: ResourceEffectIdentity = {
|
||||
@@ -2608,10 +2611,13 @@ function mountEffectImpl(
|
||||
fiberFlags: Flags,
|
||||
hookFlags: HookFlags,
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
const hook = mountWorkInProgressHook();
|
||||
const nextDeps = deps === undefined ? null : deps;
|
||||
const nextDeps = createDeps === undefined ? null : createDeps;
|
||||
currentlyRenderingFiber.flags |= fiberFlags;
|
||||
hook.memoizedState = pushSimpleEffect(
|
||||
HookHasEffect | hookFlags,
|
||||
@@ -2662,51 +2668,78 @@ function updateEffectImpl(
|
||||
}
|
||||
|
||||
function mountEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
if (
|
||||
__DEV__ &&
|
||||
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
|
||||
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
|
||||
) {
|
||||
mountEffectImpl(
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
deps,
|
||||
);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
mountResourceEffectImpl(
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
mountEffectImpl(
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
// $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it.
|
||||
create,
|
||||
createDeps,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
mountEffectImpl(
|
||||
PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
deps,
|
||||
);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
mountResourceEffectImpl(
|
||||
PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
mountEffectImpl(
|
||||
PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
// $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it.
|
||||
create,
|
||||
createDeps,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
|
||||
}
|
||||
|
||||
function mountResourceEffect(
|
||||
create: () => mixed,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
) {
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
if (
|
||||
__DEV__ &&
|
||||
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
|
||||
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
mountResourceEffectImpl(
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
|
||||
updateResourceEffectImpl(
|
||||
PassiveEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
createDeps,
|
||||
@@ -2714,6 +2747,24 @@ function mountResourceEffect(
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
// $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it.
|
||||
updateEffectImpl(PassiveEffect, HookPassive, create, createDeps);
|
||||
}
|
||||
}
|
||||
|
||||
function mountResourceEffect(
|
||||
create: () => {...} | void | null,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: {...} | void | null) => void) | void,
|
||||
) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
|
||||
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
|
||||
) {
|
||||
} else {
|
||||
mountResourceEffectImpl(
|
||||
PassiveEffect | PassiveStaticEffect,
|
||||
@@ -2730,11 +2781,11 @@ function mountResourceEffect(
|
||||
function mountResourceEffectImpl(
|
||||
fiberFlags: Flags,
|
||||
hookFlags: HookFlags,
|
||||
create: () => mixed,
|
||||
create: () => {...} | void | null,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
update: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
destroy: ((resource: {...} | void | null) => void) | void,
|
||||
) {
|
||||
const hook = mountWorkInProgressHook();
|
||||
currentlyRenderingFiber.flags |= fiberFlags;
|
||||
@@ -2752,11 +2803,11 @@ function mountResourceEffectImpl(
|
||||
}
|
||||
|
||||
function updateResourceEffect(
|
||||
create: () => mixed,
|
||||
create: () => {...} | void | null,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
update: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
destroy: ((resource: {...} | void | null) => void) | void,
|
||||
) {
|
||||
updateResourceEffectImpl(
|
||||
PassiveEffect,
|
||||
@@ -2772,11 +2823,11 @@ function updateResourceEffect(
|
||||
function updateResourceEffectImpl(
|
||||
fiberFlags: Flags,
|
||||
hookFlags: HookFlags,
|
||||
create: () => mixed,
|
||||
create: () => {...} | void | null,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
update: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
destroy: ((resource: {...} | void | null) => void) | void,
|
||||
) {
|
||||
const hook = updateWorkInProgressHook();
|
||||
const effect: Effect = hook.memoizedState;
|
||||
@@ -3938,9 +3989,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
|
||||
if (enableUseEffectEventHook) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnMount: Dispatcher = {
|
||||
readContext,
|
||||
@@ -3971,9 +4019,6 @@ const HooksDispatcherOnMount: Dispatcher = {
|
||||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
readContext,
|
||||
@@ -4004,10 +4049,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useResourceEffect =
|
||||
updateResourceEffect;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnRerender: Dispatcher = {
|
||||
readContext,
|
||||
@@ -4038,10 +4079,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
|
||||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useResourceEffect =
|
||||
updateResourceEffect;
|
||||
}
|
||||
|
||||
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
|
||||
@@ -4087,13 +4124,30 @@ if (__DEV__) {
|
||||
return readContext(context);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
mountHookTypesDev();
|
||||
checkDepsAreArrayDev(deps);
|
||||
return mountEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
checkDepsAreNonEmptyArrayDev(updateDeps);
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
checkDepsAreArrayDev(createDeps);
|
||||
return mountEffect(create, createDeps);
|
||||
}
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
@@ -4242,27 +4296,6 @@ if (__DEV__) {
|
||||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
mountHookTypesDev();
|
||||
checkDepsAreNonEmptyArrayDev(updateDeps);
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnMountWithHookTypesInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -4280,12 +4313,28 @@ if (__DEV__) {
|
||||
return readContext(context);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
updateHookTypesDev();
|
||||
return mountEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
return mountEffect(create, createDeps);
|
||||
}
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
@@ -4430,26 +4479,6 @@ if (__DEV__) {
|
||||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
updateHookTypesDev();
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnUpdateInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -4467,12 +4496,28 @@ if (__DEV__) {
|
||||
return readContext(context);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
return updateEffect(create, createDeps);
|
||||
}
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
@@ -4617,26 +4662,6 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -4654,12 +4679,28 @@ if (__DEV__) {
|
||||
return readContext(context);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
return updateEffect(create, createDeps);
|
||||
}
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
@@ -4804,26 +4845,6 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnMountInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -4847,13 +4868,29 @@ if (__DEV__) {
|
||||
return readContext(context);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
return mountEffect(create, createDeps);
|
||||
}
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
@@ -5016,27 +5053,6 @@ if (__DEV__) {
|
||||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnUpdateInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -5060,13 +5076,29 @@ if (__DEV__) {
|
||||
return readContext(context);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
return updateEffect(create, createDeps);
|
||||
}
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
@@ -5229,27 +5261,6 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -5273,13 +5284,29 @@ if (__DEV__) {
|
||||
return readContext(context);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
return updateEffect(create, createDeps);
|
||||
}
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
@@ -5442,25 +5469,4 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,26 +11,35 @@ import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {FiberRoot} from './ReactInternalTypes';
|
||||
import type {ViewTransitionInstance} from './ReactFiberConfig';
|
||||
|
||||
import {getWorkInProgressRoot} from './ReactFiberWorkLoop';
|
||||
import {
|
||||
getWorkInProgressRoot,
|
||||
getPendingTransitionTypes,
|
||||
} from './ReactFiberWorkLoop';
|
||||
|
||||
import {getIsHydrating} from './ReactFiberHydrationContext';
|
||||
|
||||
import {getTreeId} from './ReactFiberTreeContext';
|
||||
|
||||
export type ViewTransitionClassPerType = {
|
||||
[transitionType: 'default' | string]: 'none' | string,
|
||||
};
|
||||
|
||||
export type ViewTransitionClass = 'none' | string | ViewTransitionClassPerType;
|
||||
|
||||
export type ViewTransitionProps = {
|
||||
name?: string,
|
||||
children?: ReactNodeList,
|
||||
className?: 'none' | string,
|
||||
enter?: 'none' | string,
|
||||
exit?: 'none' | string,
|
||||
layout?: 'none' | string,
|
||||
share?: 'none' | string,
|
||||
update?: 'none' | string,
|
||||
onEnter?: (instance: ViewTransitionInstance) => void,
|
||||
onExit?: (instance: ViewTransitionInstance) => void,
|
||||
onLayout?: (instance: ViewTransitionInstance) => void,
|
||||
onShare?: (instance: ViewTransitionInstance) => void,
|
||||
onUpdate?: (instance: ViewTransitionInstance) => void,
|
||||
className?: ViewTransitionClass,
|
||||
enter?: ViewTransitionClass,
|
||||
exit?: ViewTransitionClass,
|
||||
layout?: ViewTransitionClass,
|
||||
share?: ViewTransitionClass,
|
||||
update?: ViewTransitionClass,
|
||||
onEnter?: (instance: ViewTransitionInstance, types: Array<string>) => void,
|
||||
onExit?: (instance: ViewTransitionInstance, types: Array<string>) => void,
|
||||
onLayout?: (instance: ViewTransitionInstance, types: Array<string>) => void,
|
||||
onShare?: (instance: ViewTransitionInstance, types: Array<string>) => void,
|
||||
onUpdate?: (instance: ViewTransitionInstance, types: Array<string>) => void,
|
||||
};
|
||||
|
||||
export type ViewTransitionState = {
|
||||
@@ -82,17 +91,49 @@ export function getViewTransitionName(
|
||||
return (instance.autoName: any);
|
||||
}
|
||||
|
||||
function getClassNameByType(classByType: ?ViewTransitionClass): ?string {
|
||||
if (classByType == null || typeof classByType === 'string') {
|
||||
return classByType;
|
||||
}
|
||||
let className: ?string = null;
|
||||
const activeTypes = getPendingTransitionTypes();
|
||||
if (activeTypes !== null) {
|
||||
for (let i = 0; i < activeTypes.length; i++) {
|
||||
const match = classByType[activeTypes[i]];
|
||||
if (match != null) {
|
||||
if (match === 'none') {
|
||||
// If anything matches "none" that takes precedence over any other
|
||||
// type that also matches.
|
||||
return 'none';
|
||||
}
|
||||
if (className == null) {
|
||||
className = match;
|
||||
} else {
|
||||
className += ' ' + match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (className == null) {
|
||||
// We had no other matches. Match the default for this configuration.
|
||||
return classByType.default;
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
export function getViewTransitionClassName(
|
||||
className: ?string,
|
||||
eventClassName: ?string,
|
||||
defaultClass: ?ViewTransitionClass,
|
||||
eventClass: ?ViewTransitionClass,
|
||||
): ?string {
|
||||
const className: ?string = getClassNameByType(defaultClass);
|
||||
const eventClassName: ?string = getClassNameByType(eventClass);
|
||||
if (eventClassName == null) {
|
||||
return className;
|
||||
}
|
||||
if (eventClassName === 'none') {
|
||||
return eventClassName;
|
||||
}
|
||||
if (className != null) {
|
||||
if (className != null && className !== 'none') {
|
||||
return className + ' ' + eventClassName;
|
||||
}
|
||||
return eventClassName;
|
||||
|
||||
+35
-10
@@ -27,6 +27,7 @@ import {
|
||||
getViewTransitionName,
|
||||
type ViewTransitionState,
|
||||
} from './ReactFiberViewTransitionComponent';
|
||||
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
|
||||
|
||||
import {
|
||||
enableCreateEventHandleAPI,
|
||||
@@ -653,7 +654,9 @@ let pendingEffectsRemainingLanes: Lanes = NoLanes;
|
||||
let pendingEffectsRenderEndTime: number = -0; // Profiling-only
|
||||
let pendingPassiveTransitions: Array<Transition> | null = null;
|
||||
let pendingRecoverableErrors: null | Array<CapturedValue<mixed>> = null;
|
||||
let pendingViewTransitionEvents: Array<() => void> | null = null;
|
||||
let pendingViewTransitionEvents: Array<(types: Array<string>) => void> | null =
|
||||
null;
|
||||
let pendingTransitionTypes: null | TransitionTypes = null;
|
||||
let pendingDidIncludeRenderPhaseUpdate: boolean = false;
|
||||
let pendingSuspendedCommitReason: SuspendedCommitReason = IMMEDIATE_COMMIT; // Profiling-only
|
||||
|
||||
@@ -695,6 +698,10 @@ export function getPendingPassiveEffectsLanes(): Lanes {
|
||||
return pendingEffectsLanes;
|
||||
}
|
||||
|
||||
export function getPendingTransitionTypes(): null | TransitionTypes {
|
||||
return pendingTransitionTypes;
|
||||
}
|
||||
|
||||
export function isWorkLoopSuspendedOnData(): boolean {
|
||||
return (
|
||||
workInProgressSuspendedReason === SuspendedOnData ||
|
||||
@@ -804,7 +811,7 @@ export function requestDeferredLane(): Lane {
|
||||
|
||||
export function scheduleViewTransitionEvent(
|
||||
fiber: Fiber,
|
||||
callback: ?(instance: ViewTransitionInstance) => void,
|
||||
callback: ?(instance: ViewTransitionInstance, types: Array<string>) => void,
|
||||
): void {
|
||||
if (enableViewTransition) {
|
||||
if (callback != null) {
|
||||
@@ -3348,9 +3355,6 @@ function commitRoot(
|
||||
pendingEffectsRemainingLanes = remainingLanes;
|
||||
pendingPassiveTransitions = transitions;
|
||||
pendingRecoverableErrors = recoverableErrors;
|
||||
if (enableViewTransition) {
|
||||
pendingViewTransitionEvents = null;
|
||||
}
|
||||
pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;
|
||||
if (enableProfilerTimer) {
|
||||
pendingEffectsRenderEndTime = completedRenderEndTime;
|
||||
@@ -3362,10 +3366,24 @@ function commitRoot(
|
||||
// might get scheduled in the commit phase. (See #16714.)
|
||||
// TODO: Delete all other places that schedule the passive effect callback
|
||||
// They're redundant.
|
||||
const passiveSubtreeMask =
|
||||
enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes)
|
||||
? PassiveTransitionMask
|
||||
: PassiveMask;
|
||||
let passiveSubtreeMask;
|
||||
if (enableViewTransition) {
|
||||
pendingViewTransitionEvents = null;
|
||||
if (includesOnlyViewTransitionEligibleLanes(lanes)) {
|
||||
// Claim any pending Transition Types for this commit.
|
||||
// This means that multiple roots committing independent View Transitions
|
||||
// 1) end up staggered because we can only have one at a time.
|
||||
// 2) only the first one gets all the Transition Types.
|
||||
pendingTransitionTypes = ReactSharedInternals.V;
|
||||
ReactSharedInternals.V = null;
|
||||
passiveSubtreeMask = PassiveTransitionMask;
|
||||
} else {
|
||||
pendingTransitionTypes = null;
|
||||
passiveSubtreeMask = PassiveMask;
|
||||
}
|
||||
} else {
|
||||
passiveSubtreeMask = PassiveMask;
|
||||
}
|
||||
if (
|
||||
// If this subtree rendered with profiling this commit, we need to visit it to log it.
|
||||
(enableProfilerTimer &&
|
||||
@@ -3461,6 +3479,7 @@ function commitRoot(
|
||||
shouldStartViewTransition &&
|
||||
startViewTransition(
|
||||
root.containerInfo,
|
||||
pendingTransitionTypes,
|
||||
flushMutationEffects,
|
||||
flushLayoutEffects,
|
||||
flushAfterMutationEffects,
|
||||
@@ -3708,11 +3727,17 @@ function flushSpawnedWork(): void {
|
||||
// effects or spawned sync work since this is still part of the previous commit.
|
||||
// Even though conceptually it's like its own task between layout effets and passive.
|
||||
const pendingEvents = pendingViewTransitionEvents;
|
||||
let pendingTypes = pendingTransitionTypes;
|
||||
pendingTransitionTypes = null;
|
||||
if (pendingEvents !== null) {
|
||||
pendingViewTransitionEvents = null;
|
||||
if (pendingTypes === null) {
|
||||
// Normalize the type. This is lazily created only for events.
|
||||
pendingTypes = [];
|
||||
}
|
||||
for (let i = 0; i < pendingEvents.length; i++) {
|
||||
const viewTransitionEvent = pendingEvents[i];
|
||||
viewTransitionEvent();
|
||||
viewTransitionEvent(pendingTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-11
@@ -47,7 +47,6 @@ export type HookType =
|
||||
| 'useRef'
|
||||
| 'useEffect'
|
||||
| 'useEffectEvent'
|
||||
| 'useResourceEffect'
|
||||
| 'useInsertionEffect'
|
||||
| 'useLayoutEffect'
|
||||
| 'useCallback'
|
||||
@@ -391,19 +390,14 @@ export type Dispatcher = {
|
||||
useContext<T>(context: ReactContext<T>): T,
|
||||
useRef<T>(initialValue: T): {current: T},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void,
|
||||
// TODO: Non-nullable once `enableUseEffectEventHook` is on everywhere.
|
||||
useEffectEvent?: <Args, F: (...Array<Args>) => mixed>(callback: F) => F,
|
||||
// TODO: Non-nullable once `enableUseResourceEffectHook` is on everywhere.
|
||||
useResourceEffect?: (
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
) => void,
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
|
||||
+31
-59
@@ -41,7 +41,6 @@ let waitFor;
|
||||
let waitForThrow;
|
||||
let waitForPaint;
|
||||
let assertLog;
|
||||
let useResourceEffect;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
describe('ReactHooksWithNoopRenderer', () => {
|
||||
@@ -70,7 +69,6 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
useDeferredValue = React.useDeferredValue;
|
||||
Suspense = React.Suspense;
|
||||
Activity = React.unstable_Activity;
|
||||
useResourceEffect = React.experimental_useResourceEffect;
|
||||
ContinuousEventPriority =
|
||||
require('react-reconciler/constants').ContinuousEventPriority;
|
||||
if (gate(flags => flags.enableSuspenseList)) {
|
||||
@@ -3311,8 +3309,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
describe('useResourceEffect', () => {
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
describe('useEffect CRUD overload', () => {
|
||||
class Resource {
|
||||
isDeleted: false;
|
||||
id: string;
|
||||
@@ -3333,36 +3331,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// @gate !enableUseResourceEffectHook
|
||||
it('is null when flag is disabled', async () => {
|
||||
expect(useResourceEffect).toBeUndefined();
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
it('validates create return value', async () => {
|
||||
function App({id}) {
|
||||
useResourceEffect(() => {
|
||||
Scheduler.log(`create(${id})`);
|
||||
}, [id]);
|
||||
return null;
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
ReactNoop.render(<App id={1} />);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'useResourceEffect must provide a callback which returns a resource. ' +
|
||||
'If a managed resource is not needed here, use useEffect. Received undefined',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('validates non-empty update deps', async () => {
|
||||
function App({id}) {
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
Scheduler.log(`create(${id})`);
|
||||
return {};
|
||||
@@ -3380,19 +3352,19 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
ReactNoop.render(<App id={1} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'useResourceEffect received a dependency array with no dependencies. ' +
|
||||
'useEffect received a dependency array with no dependencies. ' +
|
||||
'When specified, the dependency array must have at least one dependency.\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('simple mount and update', async () => {
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
|
||||
@@ -3443,13 +3415,13 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
assertLog(['destroy(2, Jack)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('simple mount with no update', async () => {
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
|
||||
@@ -3480,13 +3452,13 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
assertLog(['destroy(1, Jack)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('calls update on every render if no deps are specified', async () => {
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
|
||||
@@ -3523,10 +3495,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
assertLog(['update(2, Lauren)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
it('does not unmount previous useResourceEffect between updates', async () => {
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('does not unmount previous useEffect between updates', async () => {
|
||||
function App({id}) {
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id);
|
||||
Scheduler.log(`create(${resource.id})`);
|
||||
@@ -3562,10 +3534,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
assertLog(['update(0)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('unmounts only on deletion', async () => {
|
||||
function App({id}) {
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id);
|
||||
Scheduler.log(`create(${resource.id})`);
|
||||
@@ -3596,7 +3568,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('unmounts on deletion', async () => {
|
||||
function Wrapper(props) {
|
||||
return <App {...props} />;
|
||||
@@ -3605,7 +3577,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
|
||||
@@ -3650,10 +3622,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('handles errors in create on mount', async () => {
|
||||
function App({id}) {
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
Scheduler.log(`Mount A [${id}]`);
|
||||
return {};
|
||||
@@ -3665,7 +3637,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
Scheduler.log(`Unmount A [${id}]`);
|
||||
},
|
||||
);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
Scheduler.log('Oops!');
|
||||
throw new Error('Oops!');
|
||||
@@ -3700,10 +3672,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('handles errors in create on update', async () => {
|
||||
function App({id}) {
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
Scheduler.log(`Mount A [${id}]`);
|
||||
return {};
|
||||
@@ -3744,13 +3716,13 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
}).rejects.toThrow('Oops error!');
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('handles errors in destroy on update', async () => {
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`Mount A [${id}, ${resource.opts.username}]`);
|
||||
@@ -3800,13 +3772,13 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook && enableActivity
|
||||
// @gate enableUseEffectCRUDOverload && enableActivity
|
||||
it('composes with activity', async () => {
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
|
||||
@@ -3873,7 +3845,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
assertLog(['destroy(0, Lauren)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('composes with suspense', async () => {
|
||||
function TextBox({text}) {
|
||||
return <AsyncText text={text} ms={0} />;
|
||||
@@ -3885,7 +3857,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
|
||||
@@ -3991,7 +3963,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
// @gate enableUseEffectCRUDOverload
|
||||
it('composes with other kinds of effects', async () => {
|
||||
let rerender;
|
||||
function App({id, username}) {
|
||||
@@ -4003,7 +3975,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
useEffect(() => {
|
||||
Scheduler.log(`useEffect(${count})`);
|
||||
}, [count]);
|
||||
useResourceEffect(
|
||||
useEffect(
|
||||
() => {
|
||||
const resource = new Resource(id, opts);
|
||||
Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
|
||||
|
||||
+1
-9
@@ -38,10 +38,7 @@ import {
|
||||
} from './ReactFizzConfig';
|
||||
import {createFastHash} from './ReactServerStreamConfig';
|
||||
|
||||
import {
|
||||
enableUseEffectEventHook,
|
||||
enableUseResourceEffectHook,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {enableUseEffectEventHook} from 'shared/ReactFeatureFlags';
|
||||
import is from 'shared/objectIs';
|
||||
import {
|
||||
REACT_CONTEXT_TYPE,
|
||||
@@ -866,11 +863,6 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs
|
||||
if (enableUseEffectEventHook) {
|
||||
HooksDispatcher.useEffectEvent = useEffectEvent;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
HooksDispatcher.useResourceEffect = supportsClientAPIs
|
||||
? noop
|
||||
: clientHookNotSupported;
|
||||
}
|
||||
|
||||
export let currentResumableState: null | ResumableState = (null: any);
|
||||
export function setCurrentResumableState(
|
||||
|
||||
+11
-5
@@ -64,6 +64,8 @@ import type {
|
||||
ReactTimeInfo,
|
||||
ReactStackTrace,
|
||||
ReactCallSite,
|
||||
ReactErrorInfo,
|
||||
ReactErrorInfoDev,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {ReactElement} from 'shared/ReactElementType';
|
||||
import type {LazyComponent} from 'react/src/ReactLazy';
|
||||
@@ -3093,10 +3095,12 @@ function emitPostponeChunk(
|
||||
|
||||
function serializeErrorValue(request: Request, error: Error): string {
|
||||
if (__DEV__) {
|
||||
let message;
|
||||
let name: string = 'Error';
|
||||
let message: string;
|
||||
let stack: ReactStackTrace;
|
||||
let env = (0, request.environmentName)();
|
||||
try {
|
||||
name = error.name;
|
||||
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||
message = String(error.message);
|
||||
stack = filterStackTrace(request, error, 0);
|
||||
@@ -3110,7 +3114,7 @@ function serializeErrorValue(request: Request, error: Error): string {
|
||||
message = 'An error occurred but serializing the error message failed.';
|
||||
stack = [];
|
||||
}
|
||||
const errorInfo = {message, stack, env};
|
||||
const errorInfo: ReactErrorInfoDev = {name, message, stack, env};
|
||||
const id = outlineModel(request, errorInfo);
|
||||
return '$Z' + id.toString(16);
|
||||
} else {
|
||||
@@ -3127,13 +3131,15 @@ function emitErrorChunk(
|
||||
digest: string,
|
||||
error: mixed,
|
||||
): void {
|
||||
let errorInfo: any;
|
||||
let errorInfo: ReactErrorInfo;
|
||||
if (__DEV__) {
|
||||
let message;
|
||||
let name: string = 'Error';
|
||||
let message: string;
|
||||
let stack: ReactStackTrace;
|
||||
let env = (0, request.environmentName)();
|
||||
try {
|
||||
if (error instanceof Error) {
|
||||
name = error.name;
|
||||
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||
message = String(error.message);
|
||||
stack = filterStackTrace(request, error, 0);
|
||||
@@ -3155,7 +3161,7 @@ function emitErrorChunk(
|
||||
message = 'An error occurred but serializing the error message failed.';
|
||||
stack = [];
|
||||
}
|
||||
errorInfo = {digest, message, stack, env};
|
||||
errorInfo = {digest, name, message, stack, env};
|
||||
} else {
|
||||
errorInfo = {digest};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
import type {TransitionTypes} from 'react/src/ReactTransitionType.js';
|
||||
|
||||
import isArray from 'shared/isArray';
|
||||
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
|
||||
@@ -364,6 +365,7 @@ export function hasInstanceAffectedParent(
|
||||
|
||||
export function startViewTransition(
|
||||
rootContainer: Container,
|
||||
transitionTypes: null | TransitionTypes,
|
||||
mutationCallback: () => void,
|
||||
layoutCallback: () => void,
|
||||
afterMutationCallback: () => void,
|
||||
|
||||
@@ -59,7 +59,6 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
|
||||
@@ -33,6 +33,7 @@ export {
|
||||
unstable_getCacheForType,
|
||||
unstable_SuspenseList,
|
||||
unstable_ViewTransition,
|
||||
unstable_addTransitionType,
|
||||
unstable_useCacheRefresh,
|
||||
useId,
|
||||
useCallback,
|
||||
@@ -41,7 +42,6 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
|
||||
@@ -33,6 +33,7 @@ export {
|
||||
unstable_getCacheForType,
|
||||
unstable_SuspenseList,
|
||||
unstable_ViewTransition,
|
||||
unstable_addTransitionType,
|
||||
unstable_useCacheRefresh,
|
||||
useId,
|
||||
useCallback,
|
||||
|
||||
@@ -21,7 +21,6 @@ export {
|
||||
createElement,
|
||||
createRef,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
forwardRef,
|
||||
Fragment,
|
||||
isValidElement,
|
||||
|
||||
@@ -52,6 +52,7 @@ export {
|
||||
unstable_SuspenseList,
|
||||
unstable_TracingMarker,
|
||||
unstable_ViewTransition,
|
||||
unstable_addTransitionType,
|
||||
unstable_getCacheForType,
|
||||
unstable_useCacheRefresh,
|
||||
useId,
|
||||
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
useContext,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useInsertionEffect,
|
||||
@@ -61,10 +60,10 @@ import {
|
||||
} from './ReactHooks';
|
||||
import ReactSharedInternals from './ReactSharedInternalsClient';
|
||||
import {startTransition} from './ReactStartTransition';
|
||||
import {addTransitionType} from './ReactTransitionType';
|
||||
import {act} from './ReactAct';
|
||||
import {captureOwnerStack} from './ReactOwnerStack';
|
||||
import * as ReactCompilerRuntime from './ReactCompilerRuntime';
|
||||
import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const Children = {
|
||||
map,
|
||||
@@ -126,10 +125,8 @@ export {
|
||||
REACT_TRACING_MARKER_TYPE as unstable_TracingMarker,
|
||||
// enableViewTransition
|
||||
REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
|
||||
addTransitionType as unstable_addTransitionType,
|
||||
useId,
|
||||
act, // DEV-only
|
||||
captureOwnerStack, // DEV-only
|
||||
};
|
||||
|
||||
export const experimental_useResourceEffect: typeof useResourceEffect | void =
|
||||
enableUseResourceEffectHook ? useResourceEffect : undefined;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
||||
import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags';
|
||||
import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags';
|
||||
|
||||
type BasicStateAction<S> = (S => S) | S;
|
||||
type Dispatch<A> = A => void;
|
||||
@@ -87,11 +87,31 @@ export function useRef<T>(initialValue: T): {current: T} {
|
||||
}
|
||||
|
||||
export function useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
create: (() => (() => void) | void) | (() => {...} | void | null),
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update?: ((resource: {...} | void | null) => void) | void,
|
||||
updateDeps?: Array<mixed> | void | null,
|
||||
destroy?: ((resource: {...} | void | null) => void) | void,
|
||||
): void {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useEffect(create, deps);
|
||||
if (
|
||||
enableUseEffectCRUDOverload &&
|
||||
(typeof update === 'function' || typeof destroy === 'function')
|
||||
) {
|
||||
// $FlowFixMe[not-a-function] This is unstable, thus optional
|
||||
return dispatcher.useEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else if (typeof update === 'function') {
|
||||
throw new Error(
|
||||
'useEffect CRUD overload is not enabled in this build of React.',
|
||||
);
|
||||
}
|
||||
return dispatcher.useEffect(create, createDeps);
|
||||
}
|
||||
|
||||
export function useInsertionEffect(
|
||||
@@ -201,27 +221,6 @@ export function useEffectEvent<Args, F: (...Array<Args>) => mixed>(
|
||||
return dispatcher.useEffectEvent(callback);
|
||||
}
|
||||
|
||||
export function useResourceEffect(
|
||||
create: () => mixed,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
): void {
|
||||
if (!enableUseResourceEffectHook) {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
const dispatcher = resolveDispatcher();
|
||||
// $FlowFixMe[not-a-function] This is unstable, thus optional
|
||||
return dispatcher.useResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
}
|
||||
|
||||
export function useOptimistic<S, A>(
|
||||
passthrough: S,
|
||||
reducer: ?(S, A) => S,
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent';
|
||||
import type {TransitionTypes} from './ReactTransitionType';
|
||||
|
||||
export type SharedStateClient = {
|
||||
H: null | Dispatcher, // ReactCurrentDispatcher for Hooks
|
||||
A: null | AsyncDispatcher, // ReactCurrentCache for Cache
|
||||
T: null | BatchConfigTransition, // ReactCurrentBatchConfig for Transitions
|
||||
S: null | ((BatchConfigTransition, mixed) => void), // onStartTransitionFinish
|
||||
V: null | TransitionTypes, // Pending Transition Types for the Next Transition
|
||||
|
||||
// DEV-only
|
||||
|
||||
@@ -45,6 +47,7 @@ const ReactSharedInternals: SharedStateClient = ({
|
||||
A: null,
|
||||
T: null,
|
||||
S: null,
|
||||
V: null,
|
||||
}: any);
|
||||
|
||||
if (__DEV__) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
||||
export type TransitionTypes = Array<string>;
|
||||
|
||||
export function addTransitionType(type: string): void {
|
||||
const pendingTransitionTypes: null | TransitionTypes = ReactSharedInternals.V;
|
||||
if (pendingTransitionTypes === null) {
|
||||
ReactSharedInternals.V = [type];
|
||||
} else if (pendingTransitionTypes.indexOf(type) === -1) {
|
||||
pendingTransitionTypes.push(type);
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ export const enableInfiniteRenderLoopDetection = false;
|
||||
/**
|
||||
* Experimental new hook for better managing resources in effects.
|
||||
*/
|
||||
export const enableUseResourceEffectHook = false;
|
||||
export const enableUseEffectCRUDOverload = false;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Ready for next major.
|
||||
|
||||
@@ -203,6 +203,20 @@ export type ReactEnvironmentInfo = {
|
||||
+env: string,
|
||||
};
|
||||
|
||||
export type ReactErrorInfoProd = {
|
||||
+digest: string,
|
||||
};
|
||||
|
||||
export type ReactErrorInfoDev = {
|
||||
+digest?: string,
|
||||
+name: string,
|
||||
+message: string,
|
||||
+stack: ReactStackTrace,
|
||||
+env: string,
|
||||
};
|
||||
|
||||
export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev;
|
||||
|
||||
export type ReactAsyncInfo = {
|
||||
+type: string,
|
||||
// Stashed Data for the Specific Execution Environment. Not part of the transport protocol
|
||||
|
||||
@@ -25,6 +25,6 @@ export const enableShallowPropDiffing = __VARIANT__;
|
||||
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
|
||||
export const enableFabricCompleteRootInCommitPhase = __VARIANT__;
|
||||
export const enableSiblingPrerendering = __VARIANT__;
|
||||
export const enableUseResourceEffectHook = __VARIANT__;
|
||||
export const enableUseEffectCRUDOverload = __VARIANT__;
|
||||
export const enableOwnerStacks = __VARIANT__;
|
||||
export const enableRemoveConsolePatches = __VARIANT__;
|
||||
|
||||
@@ -25,7 +25,7 @@ export const {
|
||||
enableObjectFiber,
|
||||
enablePersistedModeClonedFlag,
|
||||
enableShallowPropDiffing,
|
||||
enableUseResourceEffectHook,
|
||||
enableUseEffectCRUDOverload,
|
||||
passChildrenWhenCloningPersistedNodes,
|
||||
enableSiblingPrerendering,
|
||||
enableOwnerStacks,
|
||||
|
||||
@@ -64,7 +64,7 @@ export const retryLaneExpirationMs = 5000;
|
||||
export const syncLaneExpirationMs = 250;
|
||||
export const transitionLaneExpirationMs = 5000;
|
||||
export const enableSiblingPrerendering = true;
|
||||
export const enableUseResourceEffectHook = false;
|
||||
export const enableUseEffectCRUDOverload = false;
|
||||
|
||||
export const enableHydrationLaneScheduling = true;
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export const renameElementSymbol = true;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const enableSiblingPrerendering = true;
|
||||
|
||||
export const enableUseResourceEffectHook = false;
|
||||
export const enableUseEffectCRUDOverload = false;
|
||||
|
||||
export const enableYieldingBeforePassive = true;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export const syncLaneExpirationMs = 250;
|
||||
export const transitionLaneExpirationMs = 5000;
|
||||
export const enableFabricCompleteRootInCommitPhase = false;
|
||||
export const enableSiblingPrerendering = true;
|
||||
export const enableUseResourceEffectHook = true;
|
||||
export const enableUseEffectCRUDOverload = true;
|
||||
export const enableHydrationLaneScheduling = true;
|
||||
export const enableYieldingBeforePassive = false;
|
||||
export const enableThrottledScheduling = false;
|
||||
|
||||
@@ -75,7 +75,7 @@ export const enableOwnerStacks = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const enableSiblingPrerendering = true;
|
||||
|
||||
export const enableUseResourceEffectHook = false;
|
||||
export const enableUseEffectCRUDOverload = false;
|
||||
|
||||
export const enableHydrationLaneScheduling = true;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export const enableSchedulingProfiler = __VARIANT__;
|
||||
export const enableInfiniteRenderLoopDetection = __VARIANT__;
|
||||
export const enableSiblingPrerendering = __VARIANT__;
|
||||
|
||||
export const enableUseResourceEffectHook = __VARIANT__;
|
||||
export const enableUseEffectCRUDOverload = __VARIANT__;
|
||||
export const enableRemoveConsolePatches = __VARIANT__;
|
||||
|
||||
// TODO: These flags are hard-coded to the default values used in open source.
|
||||
|
||||
@@ -29,7 +29,7 @@ export const {
|
||||
enableSiblingPrerendering,
|
||||
enableTransitionTracing,
|
||||
enableTrustedTypesIntegration,
|
||||
enableUseResourceEffectHook,
|
||||
enableUseEffectCRUDOverload,
|
||||
favorSafetyOverHydrationPerf,
|
||||
renameElementSymbol,
|
||||
retryLaneExpirationMs,
|
||||
|
||||
@@ -530,5 +530,6 @@
|
||||
"542": "Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary.",
|
||||
"543": "Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React.",
|
||||
"544": "Found a pair with an auto name. This is a bug in React.",
|
||||
"545": "The %s tag may only be rendered once."
|
||||
"545": "The %s tag may only be rendered once.",
|
||||
"546": "useEffect CRUD overload is not enabled in this build of React."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user