Top 5 Mistakes in React Hooks
Top 5 Mistakes in React Hooks

This are 5 most common mistakes while using React hooks.

From my perspective React hooks are difficult and the level of entrance and complexity is much higher that with usage of plain React classes. This is why so many people use them incorrectly and there are so many questions in internet about correct usage.

Don't call hooks inside conditions

You for sure heared that we should not initialze React hooks inside conditions or loops but sometime we can do it unintentionally.

import { useState } from "react";

const App = ({ id }) => {
  if (id) {
    return "Please select a ID";
  }

  const [count, setCount] = useState(0);

  return <div>{count}</div>;
};

export default App;

As you can see here we don't wrap useState with loop or condition but just rendered component with a condition on the first line. And this is entirely forbidden because it leads to the same behaviour as writing hook inside condition. This is why we are getting warning from React.

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?  react-hooks/rules-of-hooks

The correct way is always to write hooks on the top without conditions and add your rendering conditions later.

import { useState } from "react";

const App = ({ id }) => {
  const [count, setCount] = useState(0);

  if (id) {
    return "Please select a ID";
  }

  return <div>{count}</div>;
};

export default App;

This code is completely valid because we avoid writing hook inside condition.

State is not updated directly after.

One more common mistake it to use the same state directly after change thinking that it will be updated.

const App = () => {
  const [count, setCount] = useState(0);
  console.log(count);

  const increaseCount = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };

  return <div onClick={increaseCount}>{count}</div>;
};

As you can see in this example after single click count equals 1 and not 3. It problem is know as a stale state because inside a single render all values never change. So you will see update count only after rerender. The fix here is to use functional way of setting value.

  const increaseCount = () => {
    setCount((oldCount) => oldCount + 1);
    setCount((oldCount) => oldCount + 1);
    setCount((oldCount) => oldCount + 1);
  };

As you can see now we got 3.

So the hint here is if you need to calculate new state based on old state always use functional approach.

Unnecessary rerenders

It's really easy to get unnecessary rerenders of React components and actually you will spend most of your time with react hooks exactly on debugging why component is being rerendered too often. Here is one example when usage of useState will cause more renders than needed

import { useState, useEffect } from "react";

const App = () => {
  const [isFirstLoad, setIsFirstLoad] = useState(true);
  const [count, setCount] = useState(0);
  console.log("rerender", count);

  const increaseCount = () => {
    setCount((oldCount) => oldCount + 1);
  };

  useEffect(() => {
    setIsFirstLoad(false);
    console.log("It's a first load");
  }, []);

  return <div onClick={increaseCount}>hello</div>;
};

export default App;

So here we want to set the first load property for later use. But actually we don't render it in markup, it's just for some inner logic. But as we use state it still triggers rerendering of the component. If we don't need to cause rerender we can use useRef instead of useState.

import { useState, useEffect, useRef } from "react";

const App = () => {
  const isFirstLoad = useRef(true);
  const [count, setCount] = useState(0);
  console.log("rerender", count);

  const increaseCount = () => {
    setCount((oldCount) => oldCount + 1);
  };

  useEffect(() => {
    if (isFirstLoad.current) {
      isFirstLoad.current = false;
    }
    console.log("It's a first load");
  }, []);

  return <div onClick={increaseCount}>hello</div>;
};

export default App;

As you can see useRef doesn't trigger additional rerender like useState so sometimes it's a way to go.

Use effect deps

Use effect dependencies is one huge putfall for mistakes. First of all you should never ever just disable react warnings. If you get warning it means that your code is wrong, even if it works.

It's important to understand that in dependencies array of useEffect we must provide every single dependency that we use inside.

import { useState, useEffect } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  const showCount = (count) => {
    console.log("showCount", count);
  };

  useEffect(() => {
    showCount(count);
  }, []);

  return <div>hello</div>;
};

export default App;

in this case we call showCount inside useEffect to make it only once on initialize. The problem is here that we get a warning from React that we used a property inside that is not defined in dependency array.

React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

The correct way here is to always provide all things that you use inside use effect. In other way you might get difficult to debug case with stale data.

Stale closures

And the last important point is that you should avoid stale closures.

import { useState, useEffect } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(function () {
    setInterval(() => {
      console.log(`Count is: ${count}`);
    }, 2000);
  }, []);
  return (
    <div onClick={() => setCount((oldCount) => oldCount + 1)}>{count}</div>
  );
};

export default App;

As you can see here we write setInterval inside useEffect. The main problem here is that it is being defined inside the first render and it just hangs there with old count. It doesn't matter how many times we update our count. It is out of scope completely because it's a stale closure.

The solution here is to clean interval and define dependencies correctly.

import { useState, useEffect } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(
    function () {
      const id = setInterval(() => {
        console.log(`Count is: ${count}`);
      }, 2000);

      return () => clearInterval(id);
    },
    [count]
  );
  return (
    <div onClick={() => setCount((oldCount) => oldCount + 1)}>{count}</div>
  );
};

export default App;

So this were 5 popular mistakes inside React hooks

Also if you want to improve your programming skill I have lots of full courses regarding different web technologies.