1
+/**
2
+ * Exhaustive Deps
3
+ */
4
+// Valid because dependencies are declared correctly
5
+function Comment({comment, commentSource}) {
6
+ const currentUserID = comment.viewer.id;
7
+ const environment = RelayEnvironment.forUser(currentUserID);
8
+ const commentID = nullthrows(comment.id);
9
+ useEffect(() => {
10
+ const subscription = SubscriptionCounter.subscribeOnce(
11
+ `StoreSubscription_${commentID}`,
12
+ () =>
13
+ StoreSubscription.subscribe(
14
+ environment,
15
+ {
16
+ comment_id: commentID,
17
+ },
18
+ currentUserID,
19
+ commentSource
20
+ )
21
+ );
22
+ return () => subscription.dispose();
23
+ }, [commentID, commentSource, currentUserID, environment]);
24
+}
25
+
26
+// Valid because no dependencies
27
+function UseEffectWithNoDependencies() {
28
+ const local = {};
29
+ useEffect(() => {
30
+ console.log(local);
31
+ });
32
+}
33
+function UseEffectWithEmptyDependencies() {
34
+ useEffect(() => {
35
+ const local = {};
36
+ console.log(local);
37
+ }, []);
38
+}
39
+
40
+// OK because `props` wasn't defined.
41
+function ComponentWithNoPropsDefined() {
42
+ useEffect(() => {
43
+ console.log(props.foo);
44
+ }, []);
45
+}
46
+
47
+// Valid because props are declared as a dependency
48
+function ComponentWithPropsDeclaredAsDep({foo}) {
49
+ useEffect(() => {
50
+ console.log(foo.length);
51
+ console.log(foo.slice(0));
52
+ }, [foo]);
53
+}
54
+
55
+// Valid because individual props are declared as dependencies
56
+function ComponentWithIndividualPropsDeclaredAsDeps(props) {
57
+ useEffect(() => {
58
+ console.log(props.foo);
59
+ console.log(props.bar);
60
+ }, [props.bar, props.foo]);
61
+}
62
+
63
+// Invalid because neither props or props.foo are declared as dependencies
64
+function ComponentWithoutDeclaringPropAsDep(props) {
65
+ useEffect(() => {
66
+ console.log(props.foo);
67
+ // eslint-disable-next-line react-hooks/exhaustive-deps
68
+ }, []);
69
+ useCallback(() => {
70
+ console.log(props.foo);
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
72
+ }, []);
73
+ // eslint-disable-next-line react-hooks/void-use-memo
74
+ useMemo(() => {
75
+ console.log(props.foo);
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ }, []);
78
+ React.useEffect(() => {
79
+ console.log(props.foo);
80
+ // eslint-disable-next-line react-hooks/exhaustive-deps
81
+ }, []);
82
+ React.useCallback(() => {
83
+ console.log(props.foo);
84
+ // eslint-disable-next-line react-hooks/exhaustive-deps
85
+ }, []);
86
+ // eslint-disable-next-line react-hooks/void-use-memo
87
+ React.useMemo(() => {
88
+ console.log(props.foo);
89
+ // eslint-disable-next-line react-hooks/exhaustive-deps
90
+ }, []);
91
+ React.notReactiveHook(() => {
92
+ console.log(props.foo);
93
+ }, []); // This one isn't a violation
94
+}
95
+
96
+/**
97
+ * Rules of Hooks
98
+ */
99
+// Valid because functions can call functions.
100
+function normalFunctionWithConditionalFunction() {
101
+ if (cond) {
102
+ doSomething();
103
+ }
104
+}
105
+
106
+// Valid because hooks can call hooks.
107
+function useHook() {
108
+ useState();
109
+}
110
+const whatever = function useHook() {
111
+ useState();
112
+};
113
+const useHook1 = () => {
114
+ useState();
115
+};
116
+let useHook2 = () => useState();
117
+useHook2 = () => {
118
+ useState();
119
+};
120
+
121
+// Invalid because hooks can't be called in conditionals.
122
+function ComponentWithConditionalHook() {
123
+ if (cond) {
124
+ // eslint-disable-next-line react-hooks/rules-of-hooks
125
+ useConditionalHook();
126
+ }
127
+}
128
+
129
+// Invalid because hooks can't be called in loops.
130
+function useHookInLoops() {
131
+ while (a) {
132
+ // eslint-disable-next-line react-hooks/rules-of-hooks
133
+ useHook1();
134
+ if (b) return;
135
+ // eslint-disable-next-line react-hooks/rules-of-hooks
136
+ useHook2();
137
+ }
138
+ while (c) {
139
+ // eslint-disable-next-line react-hooks/rules-of-hooks
140
+ useHook3();
141
+ if (d) return;
142
+ // eslint-disable-next-line react-hooks/rules-of-hooks
143
+ useHook4();
144
+ }
145
+}
146
+
147
+/**
148
+ * Compiler Rules
149
+ */
150
+// Invalid: component factory
151
+function InvalidComponentFactory() {
152
+ const DynamicComponent = () => <div>Hello</div>;
153
+ // eslint-disable-next-line react-hooks/static-components
154
+ return <DynamicComponent />;
155
+}
156
+
157
+// Invalid: mutating globals
158
+function InvalidGlobals() {
159
+ // eslint-disable-next-line react-hooks/immutability
160
+ window.myGlobal = 42;
161
+ return <div>Done</div>;
162
+}
163
+
164
+// Invalid: useMemo with wrong deps
165
+function InvalidUseMemo({items}) {
166
+ // eslint-disable-next-line react-hooks/exhaustive-deps
167
+ const sorted = useMemo(() => [...items].sort(), []);
168
+ return <div>{sorted.length}</div>;
169
+}
170
+
171
+// Invalid: missing/extra deps in useEffect
172
+function InvalidEffectDeps({a, b}) {
173
+ useEffect(() => {
174
+ console.log(a);
175
+ // eslint-disable-next-line react-hooks/exhaustive-deps
176
+ }, []);
177
+
178
+ useEffect(() => {
179
+ console.log(a);
180
+ // TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181
+ }, [a, b]);
182
+}