DEV Community

Bruno Guimarães
Bruno Guimarães

Posted on

React Hooks, the bad side

React Hooks are a game changer that were introduced in React 16.8. that allows developers to add state and other React features to functional components. Hooks have totally transformed the way developers build React apps, making it easier to manage state and other side effects with less code and in a way that's easier to maintain. On the "good" old days, developers relied on class components to build React applications. While class components are still supported, the use of Hooks has become increasingly popular due to their ease of use and ability to make applications more performant.

To start learning React Hooks, it is important to have a solid understanding of the basics of React, such as component lifecycle, props, and state. Once you have a good grasp of these concepts, you can start exploring the different Hooks that are available in React.

That's great and all, but what's the downside to all this? Are hooks perfect? Here are four downsides to using hooks that I've learned throughout the years:

Race conditions: React Hooks are executed asynchronously, and there can be race conditions when multiple Hooks try to access the same state value at the same time.

const Counter = () => {
  const [count, setCount] = React.useState(0);

  const handleClick = () => {
    setCount(count + 1);
    console.log(count);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice that when the user clicks the Increment button, the handleClick function is called, which increments the countand logs it to the console.log. However, because setCount is asynchronous, the console.log statement may not print the updated value of count. This can lead to some unexpected behavior and incorrect data.

One way to resolve race conditions, React provides the useEffect Hook, which allows developers to specify when and how a component should re-render. By using the useEffect Hook, developers can ensure that the component is updated with the correct state values. But that reminds me, this leads to...

Re-rendering issues: If a Hook's dependencies are not correctly specified, it can cause unnecessary re-renders and negatively impact the performance of an application, consider the following useEffect Hook:

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

React.useEffect(() => {
  console.log('Count has changed!');
}, [count]);

Enter fullscreen mode Exit fullscreen mode

Notice that in this case the useEffect Hook is only supposed to run when the count state value changes. However, if we were to add an object to the dependency array, like this:

React.useEffect(() => {
  console.log('Count has changed!');
}, [count, { someObject: true }]);
Enter fullscreen mode Exit fullscreen mode

The useEffect Hook would run on every render, regardless of whether the count value has changed or not. This is because objects in JavaScript are always considered to be new values, and so the useEffect Hook will run every time.

To avoid these kinds of re-rendering issues, it is important to only include values in the dependency array that are truly necessary for the Hook to work correctly. This may involve breaking down complex objects into smaller, simpler values that are easier to manage.

It's also important to keep in mind that too few dependencies can also lead to re-rendering issues. If a Hook depends on a value that is not included in its dependency array, it may not re-render when that value changes, leading to unexpected behavior in the application that are pretty complicated to debug, which leads me to our next topic...

Debugging: Debugging Hooks can be more challenging compared to class components because Hooks are executed behind the scenes, making it harder to understand the order in which they are executed. Let's take a look on the next example:

const Counter = () => {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log('count changed');
  }, [count]);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, the component uses the useState Hook to manage its state and the useEffect Hook to log a message when the count changes. If there is an issue with the component, such as the message not being logged, it can be difficult to determine the cause of the problem because the Hooks are executed behind the scenes.

Debugging Hooks requires a deeper understanding of the lifecycle and order in which Hooks are executed. Developers need to be aware of the rules for using Hooks and understand how they interact with each other in order to effectively debug their code. In some cases, it may be helpful to use something like the React Developer Tools to examine the component's state and understand how it is being updated.

Compared to class components, where the lifecycle methods are clearly defined and executed in a specific order, debugging Hooks can be more challenging due to their implicit execution and the additional complexity introduced by asynchronous updates.

Speaking about complexity, let's talk about a little bit about:

Complex state management: Hooks can make it easier to manage state in simple use cases, but for complex state management it can get tricky. So here's some solutions:

We could use the ContextAPI as well but there's some issues like the way that the states are accessed on the React tree, for example:


const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {},
});

const UserContext = createContext({
  username: '',
  updateUsername: () => {},
});

const App = () => {
  const [theme, setTheme] = React.useState('light');
  const [username, setUsername] = React.useState('');

  return (
    <ThemeContext.Provider
      value={{
        theme,
        toggleTheme: () => setTheme(theme === 'light' ? 'dark' : 'light'),
      }}
    >
      <UserContext.Provider
        value={{
          username,
          updateUsername: (newUsername) => setUsername(newUsername),
        }}
      >
        <Header />
        <Main />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

Enter fullscreen mode Exit fullscreen mode

The problem with ThemeContext is that it cannot access anything within UserContext. To resolve this, we may require additional libraries such as Redux or Mobx.

That being said, it is important for developers to understand the pros and cons of each framework and have the ability to weigh the benefits and drawbacks. I hope this article encourages you to expand your knowledge in this area and see you in the next one.

Top comments (0)