Recommended React Hooks Convention

ilumin

Lumin

Posted on November 15, 2022

Recommended React Hooks Convention

Related Plugin

ESLint React Hooks Plugin's Rules

Rules of Hooks (error)

useHookInsideLoop

React Hook "XXX" may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render.
desc: disallowed hook inside loop

```jsx
function ComponentWithHookInsideLoop() {
  while (cond) {
    useHookInsideLoop();
  }
}
```
Enter fullscreen mode Exit fullscreen mode

useConditionalHook

React Hook "XXX" is called conditionally. React Hook must be called in the exact same order in every component render. ~Did you accidentally call a React Hook after an early return?
desc: disallowed conditionally hook

```jsx
function ComponentWithConditionalHook() {
  if (cond) {
    useConditionalHook();
  }
}

function useHook() {
  if (a) return;
  useState();
}

function useHook() {
  try {
    f();
    useState();
  } catch {}
}
```
Enter fullscreen mode Exit fullscreen mode

classHook

React Hook "XXX" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.
desc: disallowed hook in class

```jsx
class C {
  m() {
    This.useHook();
    Super.useHook();
  }
}
```
Enter fullscreen mode Exit fullscreen mode

functionError

React Hook "XXX" is called in function "SSS" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
desc: only use hook in Component or hook function

```jsx
function normalFunctionWithHook() {
  useHookInsideNormalFunction();
}
```
Enter fullscreen mode Exit fullscreen mode

topLevelError

React Hook "XXX" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.
desc: disallowed use hook at the top level

```jsx
Hook.useState();
Hook._useState();
Hook.use42();
Hook.useHook();
Hook.use_hook();
```
Enter fullscreen mode Exit fullscreen mode

useHookInsideCallbackError

React Hook "XXX" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
desc: disallowed use hook in callback or hook

```jsx
function ComponentWithHookInsideCallback() {
  useEffect(() => {
    useHookInsideCallback();
  });
}
```
Enter fullscreen mode Exit fullscreen mode

useEventError

function created with React Hook "useEvent", and can only be called from the same component. They cannot be assigned to variables or passed down

```jsx
// bad 
function MyComponent({ theme }) {
  const onClick = useEvent(() => {
    showNotification(theme);
  });
  return <Child onClick={onClick}></Child>;
}

// good 
const MyComponent = ({ theme }) => {
  const onClick = useEvent(() => {
    showNotification(theme);
  });
  return <Child onClick={() => onClick()}></Child>;
};
```
Enter fullscreen mode Exit fullscreen mode

Exhaustive Deps (warning)

Missing dependency

warn: React Hook useCallback has a missing dependency: 'props.foo'. Either include it or remove the dependency array.

function MyComponent(props) {
  useCallback(() => {
    console.log(props.foo?.toString());
  }, []);
}
Enter fullscreen mode Exit fullscreen mode

Does nothing

warn: React Hook useMemo (and useCallback) does nothing when called with only one argument. Did you forget to pass an array of dependencies?

function MyComponent(props) {
  const value = useMemo(() => { return 2*2; }); // failed
  const fn = useCallback(() => { alert('foo'); }); // failed
}
Enter fullscreen mode Exit fullscreen mode

No callback

warn: React Hook useEffect requires an effect callback. Did you forget to pass a callback to the hook?

function MyComponent() {
  useEffect()
  useLayoutEffect()
  useCallback()
  useMemo()
}
Enter fullscreen mode Exit fullscreen mode

Unnecessary dependency

warn: React Hook useMemo has an unnecessary dependency: 'local2'. Either exclude it or remove the dependency array.

function MyComponent() {
  const local1 = {};
  const local2 = {};
  useMemo(() => {
    console.log(local1);
  }, [local1, local2]);
}

// useRef doesn't cause re-render 
function MyComponent({ activeTab }) {
  const ref1 = useRef();
  const ref2 = useRef();
  useEffect(() => {
    ref1.current.scrollTop = 0;
    ref2.current.scrollTop = 0;
  }, [ref1.current, ref2.current, activeTab]);
}
Enter fullscreen mode Exit fullscreen mode

Out of scope dependency

warn: Outer scope values like 'local1' aren't valid dependencies because mutating them doesn't re-render the component.

function MyComponent() {
  const local1 = someFunc();
  function MyNestedComponent() {
    const local2 = {};
    useCallback(() => {
      console.log(local1);
      console.log(local2);
    }, [local1]); // failed
  }
}

function MyComponent() {
  useCallback(() => {}, [window]); // failed
}
Enter fullscreen mode Exit fullscreen mode

Duplicate dependency

warn: React Hook useEffect has a duplicate dependency: 'local'. Either omit it or remove the dependency array.

function MyComponent() {
  const local = {};
  useEffect(() => {
    console.log(local);
    console.log(local);
  }, [local, local]);
}
Enter fullscreen mode Exit fullscreen mode

Immutable dependency

warn: The 'foo' literal is not a valid dependency because it never changes. You can safely remove it.

function MyComponent() {
  useEffect(() => {}, ['foo']);
}
Enter fullscreen mode Exit fullscreen mode

Preferred array literator

warn: React Hook useEffect was passed a dependency list that is not an array literal. This means we can't statically verify whether you've passed the correct dependencies.

// incorrect
function MyComponent() {
  const local = {}
  const dependencies = [local];
  useEffect(() => {}, dependencies);
}

// correct
function MyComponent() {
  const local = {}
  useEffect(() => {}, [local]);
}
Enter fullscreen mode Exit fullscreen mode

Spread element dependency

warn: React Hook useEffect has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies.

function MyComponent() {
  const local = someFunc();
  useEffect(() => {
    console.log(local);
  }, [local, ...dependencies]);
}
Enter fullscreen mode Exit fullscreen mode

Complex expression dependency

warn: React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked.

function MyComponent(props) {
  useEffect(() => {
    console.log(props.items[0]);
  }, [props.items, props.items[0]]);
}
Enter fullscreen mode Exit fullscreen mode

Assign value inside hook

warn: Assignments to the 'value2' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.

function MyComponent(props) {
  let value;
  let value2;
  let value3;
  let value4;
  let asyncValue;
  useEffect(() => {
    if (value4) {
      value = {};
    }
    value2 = 100;
    value = 43;
    value4 = true;
    console.log(value2);
    console.log(value3);
    setTimeout(() => {
      asyncValue = 100;
    });
  }, []);
}
Enter fullscreen mode Exit fullscreen mode

Ref value on clean up

warn: The ref value 'myRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'myRef.current' to a variable inside the effect, and use that variable in the cleanup function.

function MyComponent() {
  const myRef = useRef();
  useEffect(() => {
    const handleMove = () => {};
    myRef.current.addEventListener('mousemove', handleMove);
    return () => myRef.current.removeEventListener('mousemove', handleMove);
  }, []);
  return <div ref={myRef} />;
}
Enter fullscreen mode Exit fullscreen mode

Always change dependency

warn: The 'handleNext' function makes the dependencies of useEffect Hook (at line 11) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'handleNext' in its own useCallback() Hook.

function MyComponent(props) {
  let [, setState] = useState();

  function handleNext(value) {
    setState(value);
  }

  useEffect(() => {
    return Store.subscribe(handleNext);
  }, [handleNext]);
}

function Component() {
  const foo = class {};
  useMemo(() => foo, [foo]);
}
Enter fullscreen mode Exit fullscreen mode

No dependency

warn: React Hook useEffect contains a call to 'setData'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [] as a second argument to the useEffect Hook.

function Hello() {
  const [data, setData] = useState(0);
  useEffect(() => {
    fetchData.then(setData);
  });
}
Enter fullscreen mode Exit fullscreen mode

Async useEffect

warn: Effect callbacks are synchronous to prevent race conditions. Put the async function inside useEffect

function Thing() {
  useEffect(async () => {}, []);
}
Enter fullscreen mode Exit fullscreen mode

Prefer inline function

warn: React Hook useEffect received a function whose dependencies are unknown. Pass an inline function instead.

function MyComponent() {
  const local = {};
  useEffect(debounce(() => {
    console.log(local);
  }, delay), []);
}
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
ilumin
Lumin

Posted on November 15, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related