Note: it was originally posted as comment under this gist: https://gist.github.com/dgcoffman/ec5014675ddfbd7b68929344234d9532
If someone wants to very quickly add such a check for useEffect
without integrating custom eslint plugin…
I created custom config for existing 'no-restricted-syntax'
rule for inspiration:
'no-restricted-syntax': [
// 1. case:
{
message: 'useEffect: please use named function instead of arrow function',
selector:
"CallExpression[callee.name='useEffect'][arguments.0.type='ArrowFunctionExpression'][arguments.0.body.body.length>1]",
},
// 2. case (for single expression in useEffect's body - allow only single function calls and single assignments)
{
message:
'useEffect: please use named function instead of arrow function (also for single expression in body)',
selector:
"CallExpression[callee.name='useEffect'][arguments.0.type='ArrowFunctionExpression'][arguments.0.body.body.length=1][arguments.0.body.body.0.type='ExpressionStatement'][arguments.0.body.body.0.expression.type!='CallExpression'][arguments.0.body.body.0.expression.type!='AssignmentExpression'][arguments.0.body.body.0.expression.type!='ChainExpression']",
},
// 3. case (for single statement in useEffect's body - allow only expression statement, return statement):
{
message:
'useEffect: please use named function instead of arrow function (also for single statement in body)',
selector:
"CallExpression[callee.name='useEffect'][arguments.0.type='ArrowFunctionExpression'][arguments.0.body.body.length=1][arguments.0.body.body.0.type!='ExpressionStatement'][arguments.0.body.body.0.type!='ReturnStatement']",
},
// 4. case (for IIFE as single expression inside useEffect's body):
{
message:
'useEffect: please use named function instead of arrow function (also for single function expression in body)',
selector:
"CallExpression[callee.name='useEffect'][arguments.0.type='ArrowFunctionExpression'][arguments.0.body.body.length=1][arguments.0.body.body.0.type='ExpressionStatement'][arguments.0.body.body.0.expression.type='CallExpression'][arguments.0.body.body.0.expression.callee.type=/((FunctionExpression)|(ArrowFunctionExpression))/]",
},
]
ℹ️ Basically first case should be enough, but you may want to cover other cases.
- case should hit all usages of useEffect with arrow function that contains more than 1 statement/expression in body:
useEffect(() => { // ❌ not allowed
initSomeSdk(API_VERSION).then(() => setInitialized(true));
initOtherSdk(API_VERSION).then(() => setOtherInitialized(true));
}, []);
useEffect(() => { // ❌ not allowed
someRef.current = someValue;
someOtherRef.current = otherValue;
}, []);
- case should hit all usages of useEffect with arrow function that contains exactly 1 expression in body, but except (function) call expression or assignment expression:
useEffect(() => { // ✅ ok for CallExpression:
initSomeSdk(API_VERSION).then(() => setInitialized(true));
}, []);
useEffect(() => { // ✅ ok for AssignmentExpression:
someRef.current = someValue;
}, []);
useEffect(() => { // ✅ ok for ChainExpression:
inputRef.current?.focus();
}, [isInputVisible]);
useEffect(() => { // ❌ not allowed for LogicalExpression:
condition && dispatch({ type: State.ERROR });
}, [condition, dispatch]);
- case should hit all usages of useEffect with arrow function that contains exactly 1 statement with one exception - return statement:
useEffect(() => { // ✅ ok
return () => {
onCloseRef.current?.();
};
}, [onCloseRef]);
useEffect(() => { // ❌ not allowed
if (something) {
…
}
}, []);
useEffect(() => { // ❌ not allowed
try {
…
} catch (e) {
…
}
}, []);
- case should hit all usages of useEffect with arrow function that contains 1 call expression in body and it is immediately invoked function expression (IIFE):
useEffect(() => { // ❌ not allowed
(function fun() {
// …
})();
}, []);
useEffect(() => { // ❌ not allowed
(() => {
// …
})();
}, []);
some useful resources for AST selectors if you want to edit these rules (selectors):