Skip to content

Instantly share code, notes, and snippets.

@lukasMega
Last active January 20, 2023 14:39
Show Gist options
  • Save lukasMega/bbdff26ded4ddd0c8e0a36eab2d8a686 to your computer and use it in GitHub Desktop.
Save lukasMega/bbdff26ded4ddd0c8e0a36eab2d8a686 to your computer and use it in GitHub Desktop.
ESLint rule: disallow arrow function in react's `useEffect`
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.

  1. 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;
}, []);
  1. 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]);
  1. 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) {
       
    }
}, []);
  1. 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):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment