[hir] Improve error message for mutating state
Sathya Gunasekaran committed
Mar 19, 2024 at 17:06 UTC
b5a7fe4e1cd69e016934dabc754adb9f99f36859
8 files changed
+81
-1
compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts
+1
@@ -261,6 +261,7 @@ const BUILTIN_HOOKS: Array<[string, BuiltInType]> = [
261
calleeEffect: Effect.Read,
262
hookKind: "useState",
263
returnValueKind: ValueKind.Frozen,
264
+ returnValueReason: ValueReason.State,
265
}),
266
],
267
[
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+5
@@ -1122,6 +1122,11 @@ export enum ValueReason {
1122
*/
1123
Context = "context",
1124
1125
+ /**
1126
+ * A value returned from `useState`
1127
+ */
1128
+ State = "state",
1129
+
1130
/**
1131
* Props of a component or arguments of a hook.
1132
*/
compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+2
@@ -1768,6 +1768,8 @@ function getWriteErrorReason(abstractValue: AbstractValue): string {
1768
return "Mutating a value returned from a function that should not be mutated.";
1769
} else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) {
1770
return "Mutating props or hook arguments is not allowed. Consider using a local variable instead.";
1771
+ } else if (abstractValue.reason.has(ValueReason.State)) {
1772
+ return "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead.";
1773
} else {
1774
return "This mutates a variable that React considers immutable.";
1775
}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md
+1
-1
@@ -21,7 +21,7 @@ function Component(props) {
21
3 | const onChange = (e) => {
22
4 | // INVALID! should use copy-on-write and pass the new value
23
> 5 | x.value = e.target.value;
24
- | ^^^^^^^ [ReactForget] InvalidReact: Mutating a value returned from a function that should not be mutated. (5:5)
24
+ | ^^^^^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (5:5)
25
6 | setX(x);
26
7 | };
27
8 | return <input value={x.value} onChange={onChange} />;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.expect.md
new
+29
@@ -0,0 +1,29 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import { useState } from "react";
6
+
7
+function Foo() {
8
+ const [state, setState] = useState({ foo: { bar: 3 } });
9
+ const foo = state.foo;
10
+ foo.bar = 1;
11
+ return state;
12
+}
13
+
14
+```
15
+
16
+
17
+## Error
18
+
19
+```
20
+ 4 | const [state, setState] = useState({ foo: { bar: 3 } });
21
+ 5 | const foo = state.foo;
22
+> 6 | foo.bar = 1;
23
+ | ^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (6:6)
24
+ 7 | return state;
25
+ 8 | }
26
+ 9 |
27
+```
28
+
29
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.js
new
+8
@@ -0,0 +1,8 @@
1
+import { useState } from "react";
2
+
3
+function Foo() {
4
+ const [state, setState] = useState({ foo: { bar: 3 } });
5
+ const foo = state.foo;
6
+ foo.bar = 1;
7
+ return state;
8
+}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.expect.md
new
+28
@@ -0,0 +1,28 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import { useState } from "react";
6
+
7
+function Foo() {
8
+ let [state, setState] = useState({});
9
+ state.foo = 1;
10
+ return state;
11
+}
12
+
13
+```
14
+
15
+
16
+## Error
17
+
18
+```
19
+ 3 | function Foo() {
20
+ 4 | let [state, setState] = useState({});
21
+> 5 | state.foo = 1;
22
+ | ^^^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (5:5)
23
+ 6 | return state;
24
+ 7 | }
25
+ 8 |
26
+```
27
+
28
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.js
new
+7
@@ -0,0 +1,7 @@
1
+import { useState } from "react";
2
+
3
+function Foo() {
4
+ let [state, setState] = useState({});
5
+ state.foo = 1;
6
+ return state;
7
+}