mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
compiler: ValidateNoRefInRender detects writes of refs
Improves ValidateNoRefAccessInRender, detecting modifications of refs during render.
Fixes #29161
ghstack-source-id: 99078b3cea
Pull Request resolved: https://github.com/facebook/react/pull/29170
This commit is contained in:
+49
-10
@@ -10,6 +10,7 @@ import {
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
Place,
|
||||
SourceLocation,
|
||||
isRefValueType,
|
||||
isUseRefType,
|
||||
} from "../HIR";
|
||||
@@ -117,7 +118,12 @@ function validateNoRefAccessInRenderImpl(
|
||||
case "MethodCall": {
|
||||
if (!isEffectHook(instr.value.property.identifier)) {
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
validateNoRefAccess(errors, refAccessingFunctions, operand);
|
||||
validateNoRefAccess(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
operand,
|
||||
operand.loc
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -138,7 +144,12 @@ function validateNoRefAccessInRenderImpl(
|
||||
});
|
||||
}
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
validateNoRefAccess(errors, refAccessingFunctions, operand);
|
||||
validateNoRefAccess(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
operand,
|
||||
operand.loc
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -146,7 +157,30 @@ function validateNoRefAccessInRenderImpl(
|
||||
case "ObjectExpression":
|
||||
case "ArrayExpression": {
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
validateNoRefAccess(errors, refAccessingFunctions, operand);
|
||||
validateNoRefAccess(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
operand,
|
||||
operand.loc
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "PropertyDelete":
|
||||
case "PropertyStore":
|
||||
case "ComputedDelete":
|
||||
case "ComputedStore": {
|
||||
validateNoRefAccess(
|
||||
errors,
|
||||
refAccessingFunctions,
|
||||
instr.value.object,
|
||||
instr.loc
|
||||
);
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
if (operand === instr.value.object) {
|
||||
continue;
|
||||
}
|
||||
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -172,12 +206,12 @@ function validateNoRefAccessInRenderImpl(
|
||||
|
||||
function validateNoRefValueAccess(
|
||||
errors: CompilerError,
|
||||
unconditionalSetStateFunctions: Set<IdentifierId>,
|
||||
refAccessingFunctions: Set<IdentifierId>,
|
||||
operand: Place
|
||||
): void {
|
||||
if (
|
||||
isRefValueType(operand.identifier) ||
|
||||
unconditionalSetStateFunctions.has(operand.identifier.id)
|
||||
refAccessingFunctions.has(operand.identifier.id)
|
||||
) {
|
||||
errors.push({
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
@@ -192,20 +226,25 @@ function validateNoRefValueAccess(
|
||||
|
||||
function validateNoRefAccess(
|
||||
errors: CompilerError,
|
||||
unconditionalSetStateFunctions: Set<IdentifierId>,
|
||||
operand: Place
|
||||
refAccessingFunctions: Set<IdentifierId>,
|
||||
operand: Place,
|
||||
loc: SourceLocation
|
||||
): void {
|
||||
if (
|
||||
isRefValueType(operand.identifier) ||
|
||||
isUseRefType(operand.identifier) ||
|
||||
unconditionalSetStateFunctions.has(operand.identifier.id)
|
||||
refAccessingFunctions.has(operand.identifier.id)
|
||||
) {
|
||||
errors.push({
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason:
|
||||
"Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)",
|
||||
loc: operand.loc,
|
||||
description: `Cannot access ref value at ${printPlace(operand)}`,
|
||||
loc: loc,
|
||||
description:
|
||||
operand.identifier.name !== null &&
|
||||
operand.identifier.name.kind === "named"
|
||||
? `Cannot access ref value \`${operand.identifier.name.value}\``
|
||||
: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ function Component(props) {
|
||||
7 | return <Foo item={item} current={current} />;
|
||||
8 | };
|
||||
> 9 | return <Items>{props.items.map((item) => renderItem(item))}</Items>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $64[13:15]:TObject<BuiltInFunction> (9:9)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
|
||||
10 | }
|
||||
11 |
|
||||
```
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ function Component(props) {
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
> 4 | const x = foo(ref);
|
||||
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $21[6:8]:TObject<BuiltInUseRefId> (4:4)
|
||||
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
5 | return x.current;
|
||||
6 | }
|
||||
7 |
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ function Component(props) {
|
||||
6 | return <Foo item={item} current={current} />;
|
||||
7 | };
|
||||
> 8 | return <Items>{props.items.map((item) => renderItem(item))}</Items>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at mutate? $60[14:16]:TObject<BuiltInFunction> (8:8)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
|
||||
9 | }
|
||||
10 |
|
||||
```
|
||||
|
||||
+6
-3
@@ -15,10 +15,13 @@ function Component(props) {
|
||||
## Error
|
||||
|
||||
```
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef(null);
|
||||
4 | ref.current = props.value;
|
||||
> 5 | return ref.current;
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $24:TObject<BuiltInRefValue> (5:5)
|
||||
> 4 | ref.current = props.value;
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $24:TObject<BuiltInRefValue> (5:5)
|
||||
5 | return ref.current;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
function Component(props) {
|
||||
const ref = useRef({ inner: null });
|
||||
ref.current.inner = props.value;
|
||||
return ref.current.inner;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
2 | function Component(props) {
|
||||
3 | const ref = useRef({ inner: null });
|
||||
> 4 | ref.current.inner = props.value;
|
||||
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
|
||||
|
||||
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $30:TObject<BuiltInRefValue> (5:5)
|
||||
5 | return ref.current.inner;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// @validateRefAccessDuringRender
|
||||
function Component(props) {
|
||||
const ref = useRef({ inner: null });
|
||||
ref.current.inner = props.value;
|
||||
return ref.current.inner;
|
||||
}
|
||||
+1
-1
@@ -25,7 +25,7 @@ function Foo({ a }) {
|
||||
3 | const ref = useRef();
|
||||
4 | // type information is lost here as we don't track types of fields
|
||||
> 5 | const val = { ref };
|
||||
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at capture $29:TObject<BuiltInUseRefId> (5:5)
|
||||
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
|
||||
6 | // without type info, we don't know that val.ref.current is a ref value so we
|
||||
7 | // *would* end up depending on val.ref.current
|
||||
8 | // however, this is an instance of accessing a ref during render and is disallowed
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender
|
||||
function useHook({ value }) {
|
||||
const ref = useRef(null);
|
||||
// Writing to a ref in render is against the rules:
|
||||
ref.current = value;
|
||||
// returning a ref is allowed, so this alone doesn't trigger an error:
|
||||
return ref;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
3 | const ref = useRef(null);
|
||||
4 | // Writing to a ref in render is against the rules:
|
||||
> 5 | ref.current = value;
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
|
||||
6 | // returning a ref is allowed, so this alone doesn't trigger an error:
|
||||
7 | return ref;
|
||||
8 | }
|
||||
```
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @validateRefAccessDuringRender
|
||||
function useHook({ value }) {
|
||||
const ref = useRef(null);
|
||||
// Writing to a ref in render is against the rules:
|
||||
ref.current = value;
|
||||
// returning a ref is allowed, so this alone doesn't trigger an error:
|
||||
return ref;
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
1 | // @validateRefAccessDuringRender:true
|
||||
2 | function Foo(props, ref) {
|
||||
> 3 | console.log(ref.current);
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $16:TObject<BuiltInRefValue> (3:3)
|
||||
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
|
||||
4 | return <div>{props.bar}</div>;
|
||||
5 | }
|
||||
6 |
|
||||
|
||||
Reference in New Issue
Block a user