Paul Cowan Contract software developer.

Frustrations with React Hooks

6 min read 1793

Frustrations With React Hooks

Editor’s note: This React Hooks article was last updated on 10 May 2021.

What problems do React Hooks solve?

Before I detail my frustrations with React Hooks, I want to state for the record that I am, for the most part, a fan of React Hooks.

I often hear that the main reason for the existence of Hooks is to replace class components. Sadly, the main heading in the official React site’s post introducing Hooks undersells Hooks with this not-so-bold statement:

Hooks are a new addition in React 16.8. They let you use state, and other React features without writing a class.

This explanation does not give me a lot of motivation to use React Hooks apart from “Classes are not cool, man!”

For my money, React Hooks allow us to address cross-cutting concerns in a much more elegant way than the previous patterns such as mixins, higher-order components and render props. Functionalities such as logging and authentication are not component-specific and React Hooks allow us to attach this type of reusable behavior to components.

What was wrong with class components?

There is something beautiful and pure about the notion of a stateless component that takes some props and returns a React element. It is a pure function and as such, side effect free.

export const Heading: React.FC = ({ level, className, tabIndex, children, ...rest }) => {
  const Tag = `h${level}` as Taggable;

  return (
    
      {children}
    
  );
};

Unfortunately, the lack of side effects makes these stateless components a bit limited, and in the end, something somewhere must manipulate state. If the component needed to maintain state between render cycles, class components were the only show in town. These class components, often called container components, execute the side effects and pass props down to these pure stateless component functions.

There are several well-documented problems with class-based lifecycle events. One of the biggest complaints is that you often have to repeat logic in componentDidMount and componentDidUpdate.

async componentDidMount() {
  const response = await get(`/users`);
  this.setState({ users: response.data });
};

async componentDidUpdate(prevProps) {
  if (prevProps.resource !== this.props.resource) {
    const response = await get(`/users`);
    this.setState({ users: response.data });
  }
};

If you have used React for any length of time, you will have encountered this problem.

With React Hooks, this side effect code can be handled in one place using the effect Hook.

We made a custom demo for .
No really. Click here to check it out.

const UsersContainer: React.FC = () => {
  const [ users, setUsers ] = useState([]);
  const [ showDetails, setShowDetails ] = useState(false);

 const fetchUsers = async () => {
   const response = await get('/users');
   setUsers(response.data);
 };

 useEffect( () => {
    fetchUsers(users)
  }, [ users ]
 );

 // etc.

The useEffect Hook is a considerable improvement, but this is a big step away from the pure stateless functions we previously had. Which brings me to my first frustration.

This is yet another JavaScript paradigm to learn

For the record, I am a 50-year-old React fanboy. The one-way data flow will always have a place in my heart after working on an ember application with the insanity of observers and computed properties.

The problem with useEffect and friends is that it exists nowhere else in the JavaScript landscape. It is unusual and has quirks, and the only way for me to become proficient and iron out these quirks is to use it in the real world and experience some pain. No tutorial using counters is going to get me into the flow. I am a freelancer and use other frameworks apart from React, and this gives me fatigue. The fact that I need to set up the eslint-plugin-react-hooks to keep me on the straight and narrow for this specific paradigm does make me feel a bit wary.

To hell and back with the dependencies array

The useEffect Hook can take an optional second argument called the dependencies array which allows you to optimize when React would execute the effect callback. React will make a comparison between each of the values via Object.is to determine whether anything has changed. If any of the elements are different than the last render cycle, then the effect will be run against the new values.

The comparison works great for primitive JavaScript types, but problems can arise if one of the elements is an object or an array. Object.is will compare objects and arrays by reference, and there is no way to override this functionality and supply a custom comparator.

Checking the equality of objects or functions by reference is a common gotcha, and I can illustrate this with the following scaled-down version of a problem I encountered:

const useFetch = (config: ApiOptions) => { 
 const [data, setData] = useState(null);
 
 useEffect(() => {
    const { url, skip, take } = config;
    const resource = `${url}?$skip=${skip}&take=${take}`;
    axios({ url: resource }).then(response => setData(response.data));
  }, [config]); // <-- will fetch on each render

  return data;
};

const App: React.FC = () => {
  const data = useFetch({ url: "/users", take: 10, skip: 0 });
  return <div>{data.map(d => <div>{d})}</div>;
};

On line 14, a new object is passed into useFetch on each render if we do not do something to ensure the same object is used each time. In this scenario, it would be preferable to check this object’s fields and not the object reference.

I do understand why React has not gone down the route of doing deep object compares. You can get into some serious performance problems if not careful. I do seem to revisit this problem a lot, and there are a number of fixes for this. The more dynamic your objects are the more workarounds you start adding.

There is an eslint plugin that you really should be using with the automatic –fix setup in your text editor of choice to apply eslint fixes automatically. I do worry about any new feature that requires an external plugin to check correctness.

The fact that use-deep-object-compare, use-memo-one and others exist is a testimony to this being a common enough problem or at the very least, a point of confusion.

React relies on the order in which Hooks are called

Some of the first custom React Hooks to hit the shelves were several useFetch implementations that use Hooks to query a remote API. Most skirt around the issue of calling the remote API from an event handler because Hooks can only be called from the start of a functional component.

What if the data we have has pagination links and we want to re-run the effect when the user clicks a link? Below is a simple useFetch example:

const useFetch = (config: ApiOptions): [User[], boolean] => {
  const [data, setData] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const { skip, take } = config;

    api({ skip, take }).then(response => {
      setData(response);
      setLoading(false);
    });
  }, [config]);

  return [data, loading];
};

const App: React.FC = () => {
  const [currentPage, setCurrentPage] = useState<ApiOptions>({
    take: 10,
    skip: 0
  });

  const [users, loading] = useFetch(currentPage);

  if (loading) {
    return <div>loading....</div>;
  }

  return (
    <>
      {users.map((u: User) => (
        <div>{u.name}</div>
      ))}
      <ul>
        {[...Array(4).keys()].map((n: number) => (
          <li>
            <button onClick={() => console.log('what do we do now?')}>{n + 1}</button>
          </li>
        ))}
      </ul>
    </>
  );
};

The useFetch Hook will be called once on the first render with this code:

  <ul>
    {[...Array(4).keys()].map((n: number) => (
      <li>
        <button onClick={() => console.log('what do we do now?')}>{n + 1}</button>
      </li>
    ))}
  </ul>

But how would we call the useFetch Hook from the event handlers of these buttons?

The rules of React Hooks clearly state:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function.

React Hooks need to be called in the same order each time the component renders. Overreacted beautifully articulates several reasons why this is the case.

You definitely cannot do this:

<button onClick={() => useFetch({ skip: n + 1 * 10, take: 10 })}>
  {n + 1}
</button>

Calling the useFetch Hook from an event handler breaks the rules of Hooks because you would break the order in which the Hooks are called on each render.

Return an executable function from the Hook

Many Hooks now return a function that can be called from outside of the top-level declaration. The rules of React Hooks still hold and the call to /api/user/1 can be triggered from an event handler.

The code below is from a package I wrote called react-abortable-fetch:

const { run, state } = useFetch(`/api/users/1`, { executeOnMount: false });
return (
  <button
    disabled={state !== 'READY'}
    onClick={() => {
      run();
    }}
  >
    DO IT
  </button>
);

The call useFetch returns an object with both a state property and a run function. The run function will actually execute the remote query.

Monitor React Hooks in production

It’s important to validate that everything works in your production React app as expected. If you’re interested in monitoring and tracking issues related to components AND seeing how users interact with specific components, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. The LogRocket React plugin allows you to search for user sessions where the user clicks a specific component in your app. With LogRocket you can understand how users interact with components, and surface any errors related to components not rendering.

In addition, LogRocket logs all actions and state from your Redux stores. LogRocket instruments your app to record requests/responses with headers + bodies. It also records the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Modernize how you debug your React apps – .

Conclusion

React Hooks have problems, and there is no getting around this fact. I have always loved React’s declarative approach, where state changes and the UI updates. The dependency arrays of useEffect and friends sound like a declarative way of making this happen.

If the dependency array only contains primitives, then this is great. Unfortunately, problems arise when you have objects and functions in the dependency array. Reference checking is how objects and functions are compared in JavaScript natively. An ill-placed arrow function will lead to a useEffect Hook spinning into an infinite loop. Developers are then left to ponder the choice between useCallback, useRef, useMemo etc., to come up with the winning formula.

The fact that React Hooks can only be called from the top-level leads to workarounds that should be handled at the framework level.

React Hooks also force you into working with closures, and I have many scars from unexpected things happening when working with closures. Stale state due to code executing in a closure is one of the problems the Hooks linter sets out to cure.

 

Paul Cowan Contract software developer.

86 Replies to “Frustrations with React Hooks”

  1. Exactly! They are admitting a design mistake and the fix is…. to create an entirely new paradigm?? Come on now.

    And the idea that code is duplicated between lifecycle methods has to be the flimsiest of excuses possible. Who are these people complaining about this? People that don’t know how to refactor code into a shared function? People that walk into doors because they forgot they had to open them first?

    Hooks seem like an odd form of bandwagoning to me. OOP is passe, and FP is the new hotness. But hooks now introduce state into functions. These are no longer the functions of FP. They are not referentially transparent pure functions. They are merely the procedures or subroutines of, say, Visual Basic, with some weird state mechanism grafted on. Side-effects everywhere!

    But you know what’s really great for state and side-effects? You know what paradigm we’ve had since the 1980s and earlier for this? OBJECTS AND CLASSES!

  2. Wow.. I didn’t realize there were others not happy with Hooks. I haven’t jumped on the Hooks bandwagon yet, hopefully i can hold off for a bit longer. I am happy to keep writing Class components as they are easy to read and understand; Readability and clarity are more important to me than some fancy new hotness; just the hooks syntax in itself is confusing the crap out of me, let alone all the gotchas that I will encounter later when I finally use hooks. Hopefully the gurus in the React team don’t find an excuse to remove Class components in React v17 !

  3. Hopefully more people do the same. I think a lot of people are scared to admit they don’t like hooks. Classes are more verbose, in a good way.

  4. Hooks sintax looks clean but there are many issues when we try to imitate lifecycles,so,i hope in next releases react team will solve better this dependency array,for now,hooks only do well job with CDM && CDU.

  5. I feel your pain as an oldtimer who has travelled widely in dev circles for over 20 years. Having spent time in Erlang and Haskell, I thought React Hooks would be the reason to get excited about React.

    However, it took me almost 30 minutes of experimentation and digging through the docs, blog posts, etc to finally understand why this didn’t trigger a re-render:
    “`
    const [flavors, setFlavorSelection] = useState(initFlavorsObj)
    setFlavorSelection( Object.assign( …flavorSelection, {[key]: (flavorSelection[key] += 1)}))
    “`
    This is the answer ( shallow comparison but I should have thought about that first! )
    “`
    setFlavorSelection( Object.assign({}, …flavorSelection, {[key]: (flavorSelection[key] += 1)}))
    “`
    I’m also starting to see folks use Hooks where better functional concepts such as curries, composition, etc are better suited. I also see custom hooks which are an awful amalgamation of noodle code. Devs who devote 80 hours to learning hooks would be better off learning something else. Let’s face it, React is really showing it’s age and Hooks do not get React closer to functional programming paradigms/frameworks like Elm or Reason

  6. After trying to use function components and hooks in a real-world project several times, I always returned to class components. Unless your function components are incredibly simple things can get very messy and complex very quickly. I do like the idea of function components and hooks, but I certainly don’t enjoy working with them in their current form.

    The React team’s reasoning for the introduction of hooks is also sketchy. Reusing code? Surely that comes as part of sensible design decisions, e.g. DRY. Not understanding how this.* works? That’s something that doesn’t just affect class instances, it affects JS as a whole; if you don’t understand it then your career as a JS/TS programmer will be going nowhere fast.

    The code base for a typical project also contains a lot more than components. Having one part of the code using one paradigm (function components), and another part of the code using a different paradigm (oop), is disconcerting at best.

    I don’t believe the React team will deprecate class components for a long time, and when they do I will be taking a cut of their source code to preserve class components, but when the team do eventually deprecate class components I imagine that will be the beginning of the end for React as we know it. Vue may become the de-facto standard at that point unless something new and magical arrives in the meantime.

    A new/separate React Component 2.0 would have been a better solution, in my opinion.

  7. Recently I was trying to use hooks in my project, ended giving up. It’s too painful to use, like writing multiple useEffect? I know it’s a ‘separate concern’ thing, but kinda verbose to me. Let alone the dependency array, messing up the lifescycle finally made me giving up, I’m sticking with class component for now.

  8. Hey Chris, I totally appreciate your frustrations because it reminds me of myself last July/August/September when I was getting up to speed with React Hooks. In contrast though, last week I had to delve into a colleague’s older class component React code and I found it painful. I suppose I’ve gotten so used to doing things “the new way” that it’s now painful to go back.

    Whenever I’m tasked with teaching someone React Hooks, I always encourage them to think about doing things “indirectly” rather than “directly”. So, for example, say we had an app where the user entered a UPC code off of a barcode and then pressed Submit to look up that product’s info.

    In my app I would wire up the `onChange` event of the Input element to a `useState` variable like this:

    “`
    const [ upc, setUpc ] = useState(null);

    setUpc(event.target.value)} />
    “`

    And then the Submit button would be wired up along with a `useState` variable like this:

    “`
    const [ isGetProductInfo, setIsGetProductInfo ] = useState(false);
    setIsGetProductInfo(true)}>Submit
    “`

    Then there would be a `useEffect` like this to fetch and save the product’s info:

    “`
    const [ productInfo, setProductInfo ] = useState(null);
    useEffect(() => {
    if (isGetProductInfo) {
    requests.get(`$(requests.API_ROOT())account_services/product_lookup/$(upc)`)
    .then(response => {
    setProductInfo(response.data);
    }
    .catch(error => { … }
    );
    setIsGetProductInfo(false);
    }
    }, [isGetProductInfo]);
    “`

    I realize this code is very basic but it illustrates a successful pattern of code to demonstrate using functional components, `useEffect`, and `useState` in React. Hope it helps!

  9. IMHO hooks are a poor implementation of functional reactive programming (FRP)… But it is a welcome first step into that direction. At least it provides local behaviors, so no need to read all component code to understand what is going on.

    Even better would be a real synchronous dataflow programming language for UIs, something like Lucid Synchrone did for DSPs. Unfortunately, earlier attempts like FlapJAX didn’t catch on, neither do new great libraries like SodiumFRP. RxJS seems to catch on, but is asynchronous, and not very suitable for UI programming due to its glitches. Maybe Svelte will do better. MobX already works for many people, but is limited compared to real FRP.

    Again IMO when you look at user interfaces as a circuit of signals, many hard programming problems become easier, and more scalable. You actually don’t need a virtual DOM either for this, Functional Reactive Programming is about taming mutable state, not avoiding it. You just connect the mutable cells of data, and let the runtime sort the dataflow graph according to ranks, and you get perfectly deterministic, testable and reason-able behavior.

    Too bad no popular frameworks takes such an approach, most likely because most of us are either imperative programmers, object oriented programmers, or functional programmers. Functional Reactive Programming is really different, it is actually dataflow programming, something that is very popular in spreadsheets, 3D software (Maya, Houdini, Substance Designer), and Digital Signal Processing. But not yet in the frontend world.

  10. I absolutely love hooks. They make state management so much easier to understand and read. I can understand frustration as it is a different way to think especially when coming from classes. But there is so much more flexibility with using hooks and they do offer performance enhancements. They open up a pandoras box of possibilities especially in the global state management realm. There is always pain when learning new things but do not get stuck in the older ways. Hooks are the future and I am excited to see them mature.

  11. Very good article…
    I have also noticed the repition of code in Lifecycle blocks of React. That was a point of concern, sometime you would have to repeat code in componentWillRecieveProps also. That was a bit furstrating.

  12. Wouldn’t it be easier just to make the useFetch depend on “skip”? Then in the click handler you could just update skip and React would handle refetching the data.

  13. Well put. A paradigm shift require a different way of thinking. The frustration and struggle coming from this article stems from trying to shoehorn hooks into the old way of thinking.

  14. I thought the same for a while, but now it’s clear to me this is fallacious. Yes, there is frustration as well but for beginners as they make the shift; After building a significant feature, I have been starting to hit these issued discussed by OP and I have scratched my head aplenty, this complexity cannot be blamed on incapability of the community at large in adaptation. A lot of people are even jumpshipping as the foresee the direction we are going, luckily for my main Product, I started out with Vue and it’s staying that way.

    These issues certainly need to be incrementally addressed within minor version(s) rather than leaving progress to radical changes like Facebook has been doing. I am skeptical at this point, perhaps they like shocking the community to keep themselves relevant, and then leave half-assed documentation to promote discussions, courses and discourse around react in general.

  15. ^THIS

    Hooks are really frustrating, it’s a step in the right direction, but 10 steps back in everything else!

    I’ve been using xState and react, mostly with an HoC pattern and it’s been working beautifully. xState is a FSM library that allows independent state machines to communicate via events/messages. I’ve adopted an approach similar to Erlang/Elixir and it has worked wonders in the frontend world.
    Components are simple functions: props in, html out. Every action send a signal/event and actors react to it, sometimes communicating with other actors, other times just triggering re renders. You get this nice conceptual graph that you can visualise and see how an action interacts with your whole app.
    Adding new functionality is usually a case of simply spent routing events to the appropriate actors, with the occasional new actor when a new behaviour is required.

  16. “and there is no way to override this functionality and supply a custom comparator.”

    what would you like to use as a comparator?

    you want to compare for example user.id it’s easy just give user.id in the dependency array instead of user…user wouldn’t make sense:

    const x = {name: “john”, age: 30};
    const y = {name: “john”, age: 30};

    console.log(x === y); // false
    console.log(Object.is(x, y)); // false

  17. Sry but I think you didn’t get hooks correctly. Hooks have states, but the state in a hook is immutable, it doesn’t change!

    Everytime you say setState(newState) in a hook, you tell React: call this function with the new value for this state (you can think of it more like function arguments).

    Unlike in Class-Components, this.state is mutable which means it changes in TIME which means side-effects!

  18. React will be eventually ruined by its hooks.
    You can see that the fundamental concept of React is to organize the HTML components into classes / objects.

    Hooks are like noobs in programming to put everything in the Global scope because they are lazy to know what is static functions, what is JS class object, how classes should be constructed, how the components should be organized, lazy to know about the mechanism in such frameworks but just do not want any restrictions in their coding to achieve their one-off results.

    I can seldom find normal piece of JS works that is properly organized.
    I am afraid that this profession has been a great volume of water inside nowadays.

    I just want to say, JS has a reason to move from class-less to class.

  19. I agree with the author that mostly I like hooks but there is hidden complexity. Maybe the complexity is just in plain site with classes but it does pose the same issues. The issue mainly are developers and their ideas on how things work based on their current understanding. With hooks it’s all too easy to place them with the component you are rendering this causes a coupling of the data to the presentation. I’ve hardly seen any code that passes the data retrieved into a pure component with no concerns with data. Hooks doesn’t lend to such a coding style as it’s not convenient to separate the “connected” component to the “non-connected” components.

    Tools will always change and have disadvantages and advantages. I hope this is a stepping stone to a better React in the future and it won’t ruin React because the grass isn’t always greener on the other side.

  20. In reality most of the complaints I see here in the comments are just people not actually using them the right way.

  21. My only true complaint around hooks is that there are too many libraries that have bandwaggoned into *only* supporting hooks, so that I’m *required* to wrap my more complex class component (because there is definitely still a strong need for both) in a functional component, just so I can pass the hook result into the class component as a prop

  22. When i first learnt ReactJS, i loved it the way class based component works and the easiest to understand. suddenly the hooks took away the limelight and forced us to learn react from scratch and shift it. now at this point of development I regret; why the hack was hooks needed? and if needed then why to put so much on it to replace the classes.. class method was a standard way of doing and i pray React gods to rethink.. Classes are more elegant way of doing things. Hooks are confusing not only developers but the end product. Better waste your time to improve the class based methods. Otherwise you won’t stand in competition for log run as hooks are not the way developers would like to approach.

    My vote for Classes.

  23. There’s a fundamental issue with using implementations like `useFetch` hook. Even though this is presented like feature of hooks.

    For ex. you have simple TODO app. You get the list using `useFetch`. Everything is good so far. The problem arises when you save a new item. If you got the list using the `useFetch` hook, you will be unable to refresh the data, without using workarounds, which defeats the purpose of using that kind of hook in the first place.

    1. Hooks can (and often should) be used inside other hooks. In your case, you should probably create a useTodoList hook, complete with data fetching (using useFetch) and state management. Then, from the useTodoList hook, you simply return the todoList state, along with functions to create/add/update data.

      1. Yes, this is the way it is typically done and I’m yet to find a better way but imho such hooks have too many responsibilities, fetching, updating and maintaining state. It’s a lot of pack into one function and yet I often see people doing this because this is the path of least resistance when working with hooks. The alternative is to have separate hooks (one for fetching and one for updating) but then you have to manage sharing state between them.

  24. Hooks seem to me to be the poster child of a leaky and bad abstraction. They look nice in simple examples, but as soon as you try to do anything with real world complexity, then you quickly end up going down a rabbit hole of dependency trees, deep properties, stale encapsulations and the like.

    For example, to create an interval or timeout callback function which examines or changes the component state you have to consider that the state variable that existed at the time the callback function was created might be a stale version of the state, so you have to make sure that you clearInterval or clearTimeout to unregister the old callback and then recreate it with something that points at the newest version of the state variable or you have to use other techniques involving “useRef” or “useCallback”.

    You end up having to spend a great deal of mental energy reasoning about how the hooks system works under the hood in order to use in all but the most basic and contrived cases. I can’t really fathom why the powers that be at Facebook thought that it would good to abstract state using immutable variables that are recreated each render, but then create a bunch of backdoors and workarounds rather than just have a simple mutable this.state. I suppose that it was probably a case of the React team needing to justify their own existence by releasing new and revolutionary features rather than just making incremental improvements.

  25. I want to lay some points from my perspective as a dev who wrote big project with both classes and hooks.

    1. useEffect

    Pros:
    – separating concerns

    – adding clarity when something should be reactive (always use useEffect dependancy array linted)

    – adding clarity where is the “logic” in the component

    – tip: keep useEffects as small as possible. For bigger ones you can replace arrow function with normal one to use the name to describe the effect: useEffect(function thisIsProductDecision() { }, []);

    – helps you to handle easily clean ups (timers, CRUD etc).

    Cons:
    – can get hard to understand if the dependancy array gets big. This is why you should strive to separate as much as possible.

    2. useState
    Pros:

    – possibility to update a single state instance and not the entire component state

    Cons:
    – makes it a bit hard if you decide to work with one object state insteaed of separated onces

    3. Class components

    I read people saying class components are easier to read and understand. I can’t agree with this statement not only because I beleive it’s just a habit but because class components:

    – Have more complex structure. You can write code which jumps trough time and space like Rick and Morty: didMount, willMount, willReceiveProps, didUpdate etc. All of those are too specific time events which can interfier with one another and puzzle the reader and the writer. I feel that useEffects and hook based React is more one diirectional and it makes you think not in “time” and “when” something will happen but in more reactive way as “why” something happened (state changed in dependancy array). Instead of mastering and deeply understand 10 lifecycle events, and understand how they can interact/interfere with eachother, with hooks you need to do that only once for only one thing: useEffect. If you master that, you mastered React.

    – Sharing business logic you must agree is not easy, readable and the solutions like HOC are running away of the declarative nature to which we should strive. Custom hooks are more prediictable, strict and easy to understand then 5 nested HOCs.

    – Classes gives the OOP feel into React, which React is not trying to do. This happened very clear with the Hook based React, where the functional path is layed in it’s roots. There is nothing wrong with OOP (probably) but if this is what you want, go to Angular.

    In the end you can create good, readable and scalable project with both classes and hooks and you can also do the opposite with both of them. React is a tool and the people working with it are responsible of the outcome. Eeither way I think that Hooks based React is a more elegant, functional and advanced tool.

    By the way I’ll appreciate if you give a go with my state manager package which is (I think) more powerfull then Redux and much much easier to use: https://www.npmjs.com/package/store-me

Leave a Reply