11import { messageIds , messages } from "./messages.js" ;
2- import { getCallExpr , getDownstreamRefs } from "./util/ast.js" ;
2+ import {
3+ findDownstreamIfs ,
4+ getCallExpr ,
5+ getDownstreamRefs ,
6+ } from "./util/ast.js" ;
37import {
48 findPropUsedToResetAllState ,
59 isUseEffect ,
@@ -14,6 +18,7 @@ import {
1418 isProp ,
1519 isHOCProp ,
1620 countStateSetterCalls ,
21+ isFnRef ,
1722} from "./util/react.js" ;
1823import { arraysEqual } from "./util/javascript.js" ;
1924
@@ -67,14 +72,38 @@ export const rule = {
6772 return ;
6873 }
6974
70- effectFnRefs
71- . filter (
72- ( ref ) =>
73- isStateSetter ( context , ref ) ||
74- ( isPropCallback ( context , ref ) &&
75- // Don't analyze HOC prop callbacks -- we don't have control over them to lift state or logic
76- ! isHOCProp ( ref . resolved ) ) ,
75+ const isAllDepsInternal = depsRefs
76+ . flatMap ( ( ref ) => getUpstreamReactVariables ( context , ref . identifier ) )
77+ . notEmptyEvery (
78+ ( variable ) =>
79+ isState ( variable ) || ( isProp ( variable ) && ! isHOCProp ( variable ) ) ,
80+ ) ;
81+
82+ findDownstreamIfs ( context , node )
83+ // An event-handling effect (invalid) and a synchronizing effect (valid)
84+ // look quite similar. But the latter should act on *all* possible states,
85+ // whereas the former waits for a specific state (from the event).
86+ // Technically synchronizing effects can be inlined too.
87+ // But an effect is arguably more readable (for once), and recommended by the React docs.
88+ . filter ( ( ifNode ) => ! ifNode . alternate )
89+ . filter ( ( ifNode ) =>
90+ getDownstreamRefs ( context , ifNode . test )
91+ . flatMap ( ( ref ) =>
92+ getUpstreamReactVariables ( context , ref . identifier ) ,
93+ )
94+ // TODO: Include non-HOC props, but probably with a different message -
95+ // the state would need to be lifted to inline the effect logic
96+ . notEmptyEvery ( ( variable ) => isState ( variable ) ) ,
7797 )
98+ . forEach ( ( ifNode ) => {
99+ context . report ( {
100+ node : ifNode . test ,
101+ messageId : messageIds . avoidEventHandler ,
102+ } ) ;
103+ } ) ;
104+
105+ effectFnRefs
106+ . filter ( isFnRef )
78107 // Non-direct calls are likely inside a callback passed to an external system like `window.addEventListener`,
79108 // or a Promise chain that (probably) retrieves external data.
80109 // Note we'll still analyze derived setters because isStateSetter considers that.
@@ -135,15 +164,6 @@ export const rule = {
135164 ) ,
136165 ) ,
137166 ) ;
138- const isAllDepsInternal = depsRefs
139- . flatMap ( ( ref ) =>
140- getUpstreamReactVariables ( context , ref . identifier ) ,
141- )
142- . notEmptyEvery (
143- ( variable ) =>
144- isState ( variable ) ||
145- ( isProp ( variable ) && ! isHOCProp ( variable ) ) ,
146- ) ;
147167
148168 if (
149169 isAllArgsInternal ||
@@ -170,20 +190,15 @@ export const rule = {
170190 messageId : messageIds . avoidChainingState ,
171191 } ) ;
172192 }
173- } else if ( isPropCallback ( context , ref ) ) {
193+ } else if (
194+ isPropCallback ( context , ref ) &&
195+ // Don't analyze HOC prop callbacks -- we don't have control over them to lift state or logic
196+ ! isHOCProp ( ref . resolved )
197+ ) {
174198 // I'm pretty sure we can flag this regardless of the arguments, including none...
175- //
176199 // Because we are either:
177200 // 1. Passing live state updates to the parent
178201 // 2. Using state as an event handler to pass final state to the parent
179- //
180- // Both are bad. However I'm not yet sure how we could differentiate #2 to give a better warning.
181- //
182- // TODO: Can we thus safely assume that state is used as an event handler when the ref is a prop?
183- // Normally we can't warn about that because we don't know what the event handler does externally.
184- // But when it's a prop, it's internal.
185- // I guess it could still be valid when the dep is external state? Or in that case,
186- // the issue is the state should be lifted to the parent?
187202 context . report ( {
188203 node : callExpr ,
189204 messageId : messageIds . avoidParentChildCoupling ,
0 commit comments