Skip to content

Commit b267667

Browse files
committed
[compiler] Improve ref validation error message
Improves the error message for ValidateNoRefAccessInRender, using the new diagnostic type as well as providing a longer but succinct summary of what refs are for and why they're unsafe to access in render.
1 parent 758686c commit b267667

File tree

34 files changed

+275
-153
lines changed

34 files changed

+275
-153
lines changed

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts

Lines changed: 100 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {CompilerError, ErrorSeverity} from '../CompilerError';
8+
import {
9+
CompilerDiagnostic,
10+
CompilerError,
11+
ErrorSeverity,
12+
} from '../CompilerError';
913
import {
1014
BlockId,
1115
HIRFunction,
@@ -388,25 +392,29 @@ function validateNoRefAccessInRenderImpl(
388392
if (fnType?.kind === 'Structure' && fnType.fn !== null) {
389393
returnType = fnType.fn.returnType;
390394
if (fnType.fn.readRefEffect) {
391-
errors.push({
392-
severity: ErrorSeverity.InvalidReact,
393-
reason:
394-
'This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)',
395-
loc: callee.loc,
396-
description:
397-
callee.identifier.name !== null &&
398-
callee.identifier.name.kind === 'named'
399-
? `Function \`${callee.identifier.name.value}\` accesses a ref`
400-
: null,
401-
suggestions: null,
402-
});
395+
errors.pushDiagnostic(
396+
CompilerDiagnostic.create({
397+
severity: ErrorSeverity.InvalidReact,
398+
category: 'Cannot access refs during render',
399+
description: ERROR_DESCRIPTION,
400+
}).withDetail({
401+
kind: 'error',
402+
loc: callee.loc,
403+
message: `This function accesses a ref value`,
404+
}),
405+
);
403406
}
404407
}
405408
for (const operand of eachInstructionValueOperand(instr.value)) {
406409
if (hookKind != null) {
407410
validateNoDirectRefValueAccess(errors, operand, env);
408411
} else {
409-
validateNoRefAccess(errors, env, operand, operand.loc);
412+
validateNoRefPassedToFunction(
413+
errors,
414+
env,
415+
operand,
416+
operand.loc,
417+
);
410418
}
411419
}
412420
env.set(instr.lvalue.identifier.id, returnType);
@@ -449,7 +457,7 @@ function validateNoRefAccessInRenderImpl(
449457
) {
450458
safeBlocks.delete(block.id);
451459
} else {
452-
validateNoRefAccess(errors, env, instr.value.object, instr.loc);
460+
validateNoRefUpdate(errors, env, instr.value.object, instr.loc);
453461
}
454462
for (const operand of eachInstructionValueOperand(instr.value)) {
455463
if (operand === instr.value.object) {
@@ -583,18 +591,17 @@ function destructure(
583591

584592
function guardCheck(errors: CompilerError, operand: Place, env: Env): void {
585593
if (env.get(operand.identifier.id)?.kind === 'Guard') {
586-
errors.push({
587-
severity: ErrorSeverity.InvalidReact,
588-
reason:
589-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
590-
loc: operand.loc,
591-
description:
592-
operand.identifier.name !== null &&
593-
operand.identifier.name.kind === 'named'
594-
? `Cannot access ref value \`${operand.identifier.name.value}\``
595-
: null,
596-
suggestions: null,
597-
});
594+
errors.pushDiagnostic(
595+
CompilerDiagnostic.create({
596+
severity: ErrorSeverity.InvalidReact,
597+
category: 'Cannot access refs during render',
598+
description: ERROR_DESCRIPTION,
599+
}).withDetail({
600+
kind: 'error',
601+
loc: operand.loc,
602+
message: `Cannot access ref value during render`,
603+
}),
604+
);
598605
}
599606
}
600607

@@ -608,22 +615,47 @@ function validateNoRefValueAccess(
608615
type?.kind === 'RefValue' ||
609616
(type?.kind === 'Structure' && type.fn?.readRefEffect)
610617
) {
611-
errors.push({
612-
severity: ErrorSeverity.InvalidReact,
613-
reason:
614-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
615-
loc: (type.kind === 'RefValue' && type.loc) || operand.loc,
616-
description:
617-
operand.identifier.name !== null &&
618-
operand.identifier.name.kind === 'named'
619-
? `Cannot access ref value \`${operand.identifier.name.value}\``
620-
: null,
621-
suggestions: null,
622-
});
618+
errors.pushDiagnostic(
619+
CompilerDiagnostic.create({
620+
severity: ErrorSeverity.InvalidReact,
621+
category: 'Cannot access refs during render',
622+
description: ERROR_DESCRIPTION,
623+
}).withDetail({
624+
kind: 'error',
625+
loc: (type.kind === 'RefValue' && type.loc) || operand.loc,
626+
message: `Cannot access ref value during render`,
627+
}),
628+
);
629+
}
630+
}
631+
632+
function validateNoRefPassedToFunction(
633+
errors: CompilerError,
634+
env: Env,
635+
operand: Place,
636+
loc: SourceLocation,
637+
): void {
638+
const type = destructure(env.get(operand.identifier.id));
639+
if (
640+
type?.kind === 'Ref' ||
641+
type?.kind === 'RefValue' ||
642+
(type?.kind === 'Structure' && type.fn?.readRefEffect)
643+
) {
644+
errors.pushDiagnostic(
645+
CompilerDiagnostic.create({
646+
severity: ErrorSeverity.InvalidReact,
647+
category: 'Cannot access refs during render',
648+
description: ERROR_DESCRIPTION,
649+
}).withDetail({
650+
kind: 'error',
651+
loc: (type.kind === 'RefValue' && type.loc) || loc,
652+
message: `Passing a ref to a function may read its value during render`,
653+
}),
654+
);
623655
}
624656
}
625657

626-
function validateNoRefAccess(
658+
function validateNoRefUpdate(
627659
errors: CompilerError,
628660
env: Env,
629661
operand: Place,
@@ -635,18 +667,17 @@ function validateNoRefAccess(
635667
type?.kind === 'RefValue' ||
636668
(type?.kind === 'Structure' && type.fn?.readRefEffect)
637669
) {
638-
errors.push({
639-
severity: ErrorSeverity.InvalidReact,
640-
reason:
641-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
642-
loc: (type.kind === 'RefValue' && type.loc) || loc,
643-
description:
644-
operand.identifier.name !== null &&
645-
operand.identifier.name.kind === 'named'
646-
? `Cannot access ref value \`${operand.identifier.name.value}\``
647-
: null,
648-
suggestions: null,
649-
});
670+
errors.pushDiagnostic(
671+
CompilerDiagnostic.create({
672+
severity: ErrorSeverity.InvalidReact,
673+
category: 'Cannot access refs during render',
674+
description: ERROR_DESCRIPTION,
675+
}).withDetail({
676+
kind: 'error',
677+
loc: (type.kind === 'RefValue' && type.loc) || loc,
678+
message: `Cannot update ref during render`,
679+
}),
680+
);
650681
}
651682
}
652683

@@ -657,17 +688,22 @@ function validateNoDirectRefValueAccess(
657688
): void {
658689
const type = destructure(env.get(operand.identifier.id));
659690
if (type?.kind === 'RefValue') {
660-
errors.push({
661-
severity: ErrorSeverity.InvalidReact,
662-
reason:
663-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
664-
loc: type.loc ?? operand.loc,
665-
description:
666-
operand.identifier.name !== null &&
667-
operand.identifier.name.kind === 'named'
668-
? `Cannot access ref value \`${operand.identifier.name.value}\``
669-
: null,
670-
suggestions: null,
671-
});
691+
errors.pushDiagnostic(
692+
CompilerDiagnostic.create({
693+
severity: ErrorSeverity.InvalidReact,
694+
category: 'Cannot access refs during render',
695+
description: ERROR_DESCRIPTION,
696+
}).withDetail({
697+
kind: 'error',
698+
loc: type.loc ?? operand.loc,
699+
message: `Cannot access ref value during render`,
700+
}),
701+
);
672702
}
673703
}
704+
705+
const ERROR_DESCRIPTION =
706+
'React refs are values that are not needed for rendering. Refs should only be accessed ' +
707+
'outside of render, such as in event handlers or effects. ' +
708+
'Accessing a ref value (the `current` property) during render can cause your component ' +
709+
'not to update as expected (https://react.dev/reference/react/useRef)';

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,46 +34,54 @@ export const FIXTURE_ENTRYPOINT = {
3434
```
3535
Found 4 errors:
3636
37-
Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
37+
Error: Cannot access refs during render
38+
39+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
3840
3941
error.capture-ref-for-mutation.ts:12:13
4042
10 | };
4143
11 | const moveLeft = {
4244
> 12 | handler: handleKey('left')(),
43-
| ^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
45+
| ^^^^^^^^^^^^^^^^^ This function accesses a ref value
4446
13 | };
4547
14 | const moveRight = {
4648
15 | handler: handleKey('right')(),
4749
48-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
50+
Error: Cannot access refs during render
51+
52+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
4953
5054
error.capture-ref-for-mutation.ts:12:13
5155
10 | };
5256
11 | const moveLeft = {
5357
> 12 | handler: handleKey('left')(),
54-
| ^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
58+
| ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
5559
13 | };
5660
14 | const moveRight = {
5761
15 | handler: handleKey('right')(),
5862
59-
Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
63+
Error: Cannot access refs during render
64+
65+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
6066
6167
error.capture-ref-for-mutation.ts:15:13
6268
13 | };
6369
14 | const moveRight = {
6470
> 15 | handler: handleKey('right')(),
65-
| ^^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
71+
| ^^^^^^^^^^^^^^^^^^ This function accesses a ref value
6672
16 | };
6773
17 | return [moveLeft, moveRight];
6874
18 | }
6975
70-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
76+
Error: Cannot access refs during render
77+
78+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
7179
7280
error.capture-ref-for-mutation.ts:15:13
7381
13 | };
7482
14 | const moveRight = {
7583
> 15 | handler: handleKey('right')(),
76-
| ^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
84+
| ^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
7785
16 | };
7886
17 | return [moveLeft, moveRight];
7987
18 | }

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,28 @@ export const FIXTURE_ENTRYPOINT = {
2222
```
2323
Found 2 errors:
2424
25-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
25+
Error: Cannot access refs during render
26+
27+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2628
2729
error.hook-ref-value.ts:5:23
2830
3 | function Component(props) {
2931
4 | const ref = useRef();
3032
> 5 | useEffect(() => {}, [ref.current]);
31-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
33+
| ^^^^^^^^^^^ Cannot access ref value during render
3234
6 | }
3335
7 |
3436
8 | export const FIXTURE_ENTRYPOINT = {
3537
36-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
38+
Error: Cannot access refs during render
39+
40+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
3741
3842
error.hook-ref-value.ts:5:23
3943
3 | function Component(props) {
4044
4 | const ref = useRef();
4145
> 5 | useEffect(() => {}, [ref.current]);
42-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
46+
| ^^^^^^^^^^^ Cannot access ref value during render
4347
6 | }
4448
7 |
4549
8 | export const FIXTURE_ENTRYPOINT = {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ function Component(props) {
1717
```
1818
Found 1 error:
1919
20-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
20+
Error: Cannot access refs during render
21+
22+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2123
2224
error.invalid-access-ref-during-render.ts:4:16
2325
2 | function Component(props) {
2426
3 | const ref = useRef(null);
2527
> 4 | const value = ref.current;
26-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
28+
| ^^^^^^^^^^^ Cannot access ref value during render
2729
5 | return value;
2830
6 | }
2931
7 |

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ function Component(props) {
2121
```
2222
Found 1 error:
2323
24-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
24+
Error: Cannot access refs during render
25+
26+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2527
2628
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33
2729
7 | return <Foo item={item} current={current} />;
2830
8 | };
2931
> 9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
30-
| ^^^^^^^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
3133
10 | }
3234
11 |
3335
```

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-current-inferred-ref-during-render.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ component Example() {
2020
```
2121
Found 1 error:
2222
23-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
23+
Error: Cannot access refs during render
24+
25+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2426
2527
4 | component Example() {
2628
5 | const fooRef = makeObject_Primitives();
2729
> 6 | fooRef.current = true;
28-
| ^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
30+
| ^^^^^^^^^^^^^^ Cannot update ref during render
2931
7 |
3032
8 | return <Stringify foo={fooRef} />;
3133
9 | }

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ function Component() {
1818
```
1919
Found 1 error:
2020
21-
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
21+
Error: Cannot access refs during render
22+
23+
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
2224
2325
error.invalid-disallow-mutating-ref-in-render.ts:4:2
2426
2 | function Component() {
2527
3 | const ref = useRef(null);
2628
> 4 | ref.current = false;
27-
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
29+
| ^^^^^^^^^^^ Cannot update ref during render
2830
5 |
2931
6 | return <button ref={ref} />;
3032
7 | }

0 commit comments

Comments
 (0)