rules-of-hooks-component

Try in Playground
react-best-practicesUnknownInformational

0

No tags

No CWE or CVE

Ast Rule: assignment


rules-of-hooks-component

How to write a rule
function visit(node, filename, code) {
  const REACT_HOOKS_NAMES = [
    "useState",
    "useEffect",
    "useContext",
    "useReducer",
    "useCallback",
    "useRef",
    "useMemo"
  ]

  const flagHook = (element, message) => {
    const error = buildError(element.start.line, element.start.col,
      element.end.line, element.end.col,
      message, "WARNING", "BEST_PRACTICE");
    addError(error);
  }


  // is there a hook usage from this node?
  const containsHook = (element) => {
    if (!element) {
      return false;
    }
    if (element.astType === "functioncall") {
      const name = element.functionName.value;
      return (REACT_HOOKS_NAMES.includes(name));
    }
		if (element.astType === "sequence") {
      return element.elements.map((e) => containsHook(e)).includes(true);
    }
		if (element.astType === "variabledeclaration") {
			return containsHook(element.value);
		}
    return false;
  }

  // indicate if we have a return statement from a given node
  const containsReturnStatement = (element) => {
    if (!element) {
      return false;
    }

    if (element.astType === "return") {
      return true;
    }
    if (element.astType === "sequence") {
      return element.elements.map((e) => {
        return containsReturnStatement(e);
      }).includes(true);
    }
    return false;
  };


  // do we have a if statement with a return inside
  const containsIfWithReturn = (element) => {
		
    if (!element || element === null) {
      return false;
    }
    if (element.astType === "sequence") {
      element.elements.forEach((e) => {
        if (containsIfWithReturn(e)) {
          return true;
        }
      });
    }
    if (element.astType === "ifstatement") {

      return containsReturnStatement(element.statements) || containsReturnStatement(element.elseStatements);
    }
    return false;
  }


  const checkHookUsageInsideFunctionDefinition = (element) => {

    // check if a function content ever calls a hook
    const checkFunctionContent = (element) => {
      if (element.astType === "functionexpression") {
        checkFunctionContent(element.content);
      }
      if (element.astType === "sequence") {
        element.elements.forEach((e) => checkFunctionContent(e));
      }

      if (element.astType === "functioncall") {
        if (element.arguments && element.arguments.values) {
          element.arguments.values.forEach((v) => {
            checkFunctionContent(v.value);
          });
        }
      }

      if (containsHook(element)) {
        flagHook(element, "hook should not be called inside inner functions");
      }

    };


    if (!element) {
      return;
    }
    if (element.astType === "variabledeclaration") {
      checkHookUsageInsideFunctionDefinition(element.value);
    }

    if (element.astType === "functionexpression") {
      checkFunctionContent(element);
    }
  };



  // main entry point
  if (node.right.astType === "functionexpression") {

    const functionContent = node.right.content;
    var hasIfWithReturn = false;
    if (functionContent.astType === "sequence") {
      /**
       * Check for potential use of a hook inside a function there
       */
      functionContent.elements.forEach(e => {
        checkHookUsageInsideFunctionDefinition(e);
      })
      /**
       * Check for each element if we found a if with a return inside.
       * If there is a if with a return, any subsequent hook will have an error
       */
      functionContent.elements.forEach(e => {

        const isHook = containsHook(e);
        if (!hasIfWithReturn) {
          const t = containsIfWithReturn(e);
          if (t) {
            hasIfWithReturn = true;
          }
        }
        if (isHook && hasIfWithReturn) {
          flagHook(e, "hook should not be placed after a if that returns");
        }
      });
    }
  }
}

callback.js

Expected test result: has error

const useMyCustomHook = (isReady) => {
  if (!isReady){
		return;
	}
  
  // invalid
  const [state, setState] = useState();
  
  // invalid
  const myCallback = useCallback(() => {
    // invalid
    useEffect(function myEffect() {
      return;
    });
  }, []);
  
  // invalid 
  const value = useMemo(() => ({ state, myCallback}), []);
  
  return value;
}

test-hook-inside-function.js

Expected test result: has error

const MyComp = () => {
  const [payload, setPayload] = useState();
  const [result, setResult] = useState();
  
  const handleClick = () => {
    // invalid
    setPayload(useContext(MyContext));
  };
  
  useEffect(() => {
    fetch(payload).then((res) => {
      setResult(res);
    });
  }, [payload]);

  return <button onClick={handleClick}>my button</button>;
}

use-before-if.js

Expected test result: no error

const myComponent = () => {
  const { data, loading } = useQuery();
  
	// invalid
  useEffect(() => {
    // do something
  }, [data]);
  
  if (loading) {
    return (<div></div>);
  }
  

  return (<div>data.example</div>);
}

use-effect-after-if.js

Expected test result: has error

Add comment

Log in to add a comment


    Be the first one to leave a comment!

Codiga Logo
Codiga Hub
  • Rulesets
  • Playground
  • Snippets
  • Cookbooks
soc-2 icon

We are SOC-2 Compliance Certified

G2 high performer medal

Codiga – All rights reserved 2022.