React Hooks: The Dangers Probably No One Told You About

Explore the dangers React introduced with hooks and how to stay ahead and avoid them.

Adnan Sahinovic
Geek Culture
5 min readSep 30, 2022

--

Photo by Oscar Omondi on Unsplash

Since the introduction of hooks in React 16.8, a few years have passed, and most developers have abandoned the old class structure.

Hooks made it easy to extract the logic and an easy way to reuse it. After the initial release, they took the front-end world by storm.

In the past few years, I found dozen hooks-related problems in my and by reviewing others’ code. I’ll try to share the dangers I encountered using hooks, so you don’t have to make the same mistakes.

We often take mainstream innovations for granted, and rarely we’re critical enough about them. I believe new tools should also be measured by how easy it is to introduce new bugs or create bad coding habits.

Don’t get me wrong — hooks are everything the old-school React lacked, as there was no easy way to extract and share logic between components. Developers had to deal with HOC (High Order Components) or Render Props. Both of these patterns were too difficult for most to use.

If you don’t know anything about the hooks or need a refresher, here is a good one. Dan Abramov — Making sense of react hooks

1. Dependencies

Strings, booleans, numbers, and other primitives are straightforward to add as dependencies to useEffect. However, you can't use arrays and objects as dependencies. Reason — they're not comparable in a simple way.

By default, arrays and objects will compare by reference.

These dependencies may affect the execution of useEffect hooks in these two cases:

  • Array or object is the same - JS will compare them using different references
  • Array or object have different values - JS will compare them using the same reference.

In both cases, the hook will lead to bugs, and they won't perform correctly.

There are known hacks and tricks to overcome this issue. None of these is the perfect solution, but sometimes we just need to make things work.

  • The first option is to use JSON.stringify()
  • Another (ES6) option would be to use template literals to turn them into strings. Similar to JSON.stringify(), except the result won't be wrapped in an array.
  • The third option, if the array size doesn't change, would be to use the spread operator:

2. Conditionals

Hooks rely on the order of execution, so it isn’t possible to call a hook conditionally.

Using hooks conditional would change the order, and your Linter would complain. This issue reduces the way we can structure hooks.

Technically, you can’t work with hooks in conditionals. However, if you know how they work internally, you can make conditional hooks work for you.

You need to create two or more components for each hook you want to render. In each component, you can add its logic.

The logic for conditional rendering
Hook that will be conditionally rendered.

This way, we can render two different hooks based on the userId variable.

3. Issue with useEffect

Developers use useEffect to modify state, DOM, or make API calls.

I can’t even think of how often I’ve been asked, “why is useEffect called twice?” or “Why do I have an infinite loop in useEffect?”.

So why is useEffect bad in the first place?

  • The good thing about useEffect: You can use it almost for everything
  • The bad thing about useEffect: You can use it almost for everything

Infinite loop issue

https://stackoverflow.com/questions/53070970/infinite-loop-in-useeffect — This issue was not the only one — added 11 months ago and seen by 175k developers.

The most straightforward way to accomplish an infinite loop is to trigger the effect when some state changes, and when it does, you run code that triggers this exact state change.

The fastest solution is to remove the dependency causing this problem.

You should always use multiple useEffect to keep the single responsibility principle. The fewer dependencies you have, the fewer bugs you could potentially have.

Too much code inside of this hook could get you into problems. Take some time to extract and refactor functions inside of useEffect .

4. Overusing useCallback & useMemo

Fresh React developers often think it’s a safer approach to memoize function than it’s not. When needed, memoization provides a powerful set of tools to prevent unneeded rendering.

However, everything too good comes with a high price. By using useCallback and useMemo we increase code complexity, which affects the speed of development and can introduce bugs in the long term.

Performance optimizations are not free. We should think of useMemo and useCallback as fine-tuning for apps.

When NOT to optimise

As powerful as they are, you shouldn’t use useMemo or useCallback for every function. Before using, consider checking app performance first. It’s often better to start improving your code before adding these hooks.

Most of the time, developers shouldn’t bother optimizing unnecessary rerenders. React is fast by nature; I can’t think that I’ve ever experienced performance issues using it. — This isn’t true for React Native and mobile app development.

These two can’t fix harmful code, but if everything is already in place, it can take your code from good — great.

Cases when it’s BETTER to optimise

You can use useMemo when working with functions where inputs gradually change. Also, when data is large enough to cause memory issues, or when parameters are large so that the costs of comparison doesn’t outweigh the use of the wrapper.

useCallback works well in instances where the code would otherwise be recompiled with every call. Memoizing the results can decrease the cost of calling functions over and over again when the inputs change over time.

Expensive calculations with useMemo

The benefit of useMemo is that you can get a value like this:

const a1 = {b: props.b}

And get it lazily:

const a2 = useMemo(() => ({b: props.b}), [props.b])

For this case useMemo isn’t useful, but imagine a function that calculates a value which is computationally expensive to calculate. Not a lot of apps do this, but in case they do — useMemo is a perfect solution.

Usually, If you start noticing rendering issues, it’s a perfect moment to optimise rerendering.

Happy coding! ✋

--

--

Adnan Sahinovic
Geek Culture

Building tools on phones and browsers and documenting my expedition. React-Native since v0.5 • Android dev since KitKat