DEV Community

Kent C. Dodds
Kent C. Dodds

Posted on

What's hard about React Hooks for you?

As you've been using/learning about React Hooks, what have been the hardest parts for you? #discuss

Top comments (66)

Collapse
 
maxart2501 profile image
Massimo Artizzu • Edited

They work like black magic.

It's actually kind of clear why they behave like they do (they're called in sequence), but the consequences in terms of code organization and structure are... meaningful, at least, if not outright jarring.

You can't have a hook called inside an if branch, for start. That's enough to be a game changer. But then, the usual lifecycle hooks like componentDidMount and so on are all condensed inside useEffect with its special second argument logic.

With classes we had a clear idea of the lifecycle of a component. With hooks it's distributed inside a lot of use* functions with often not really meaningful names.

Not that the names chosen by the React team are clear either, mind you. useCallback, useReducer, useLayoutEffect and so on aren't IMO clear at all about their intent and usage. Compare those names with the aforementioned componentDidMount.

I've found myself read the documentation about them more times than I'd have liked to. And I think I'll do that again on the next project 😔

All in all, I think hooks are a clever and simple solution to most of the reactive application problems, but they're a shifting paradigm - not that it's unsurmountable but enough to make "classic" React something completely alien. At this point, would it be so bad to fork React into a hooks-only separate library?

Collapse
 
georgecoldham profile image
George

At this point, would it be so bad to fork React into a hooks-only separate library?

I agree with this, but I feel that its way too late now. Also is the intention to eventually fade out "classic" react?

Collapse
 
maxart2501 profile image
Massimo Artizzu

I agree with this, but I feel that its way too late now.

Interesting statement. Hooks became stable on Fabruary 6th with React 16.8, not even 6 months ago. When do you think it was the appropriate time? Maybe before so many libraries using hooks popped out like mushrooms after the rain? 😅

Also is the intention to eventually fade out "classic" react?

They (React maintainers) said there's no intention to dismiss class components in the foreseeable future. But what about the hidden intentions? In the end, the explicit goal was to push developers away from class components.

Thread Thread
 
georgecoldham profile image
George

Was release only ~6 months ago? I started playing with hooks before full release though, so feels longer to me I guess...

I feel like the right time should have been when I was announced and was known to be so dramatically different. Have it as a React add-on. I imagine that there would be way less usage of it if that was the case though.

Idk really, maybe the ideal solution would be to go the babel route? @React/core with optional @React/classes and @React/hooks or something.

Thread Thread
 
kentcdodds profile image
Kent C. Dodds

I expect that classes will eventually be extracted to a separate package and a codemod will be made available to update code to use that package, similar to what was done with React.createClass. The react team is very interested in making it easier to build UIs with React and they feel that hooks is the way to do this. So putting hooks in a separate package wouldn't have pushed their goals forward very well. I'm happy with the way it's been done so far. It's a change, and that comes with a learning curve, but it's one that I'm glad to have for the benefits that hooks provide.

Thread Thread
 
maxart2501 profile image
Massimo Artizzu

That's an interesting insight, Kent. Thank you!

Collapse
 
evolutionxbox profile image
Jonathan Cousins

They work like black magic

Consider watching youtube.com/watch?v=KJP1E-Y-xyo

Collapse
 
maxart2501 profile image
Massimo Artizzu

If I have to consider something, give me a hint of what it's about 😄

Anyway, interesting video, very clear. Shawn is excellent at explaining stuff.

But, IMO, the point still remain. A developer could learn those things, but they shouldn't be forced to do so. They're glimpses of the internals of hooks. But, as Shawn says at the end of the video, that's not React - so it's unclear how, for example, removing a component from the tree affects the hooks array. Or why you can't use hooks inside class components. Or why there's a useEffect hook but also a useLayoutEffect and how - or why! - they differ.

This is complicated by the fact that function components do have a lifecycle just like class components, but it's hidden under the rug of hooks.

Thread Thread
 
evolutionxbox profile image
Jonathan Cousins

I personally think it’s only complicated because we’re so steeped in OOP and lifecycle methods this seems so alien.

Thread Thread
 
maxart2501 profile image
Massimo Artizzu

Yes, that indeed might be the case.

I also wonder why, at this point, it is the case. I.e., why a lifecycle seems so natural at this point. It's not like someone imposed this concept on everyone - maybe it's just because we usually see something "coming to life", "living" and "dying" that we apply this idea even to application components 😬

Also, React indeed has a "cycle" to make things work - the work loop. Which works well with JavaScript's event loop too.

In the end, I'm unsure if there are solid, evident advantages about hooks that makes us say that they're definitely the better way to create (React) components. They comes with compromises, like basically everything else, and I'm unconvinced that the drawbacks could be nullified by mere habit.

But for now, I still think it's too soon to declare something.

Thread Thread
 
dperetti profile image
Dominique PERETTI

My feeling at the moment is that Hooks is an attempt to solve issues they created themselves by pushing functional programming too far (HOC wrapper hell, etc.).
That's weird because it's pretty easy and elegant to avoid those problems if you use the right OOP patterns (adapters, dependency injection...).
I wish they had improved the class usage rather than introducing those terrible, anti-pattern, useThings.
They are shiny and new, they are nice for small components and todo demo apps, but for large apps, the problems remain.

Thread Thread
 
evolutionxbox profile image
Jonathan Cousins

That's an interesting take as they're using closures. Which as you may know isn't new, nor specifically "functional".

HOC wrapper hell is also interesting as in my experience most HOCs issues are down to bad architecture and exhibit similar as inheritance.

"They are shiny and new" - they're old. Only new in React land.

Collapse
 
jruddell profile image
John Ruddell

To add to this already great comment, hooks have felt more like a fad, not only in the way it was introduced but also the way it was adopted. For example, hooks was released partly to help with newer developers that don't understand classes. Some of my primary concerns are that the JavaScript community and ES standards have been heading in the direction of more OOP with ES6 -> ESNext. Hooks are a fork essentially moving away from that and more down the functional route. I agree there are times and places for hooks, but I feel they can be overused.

For instance, when you come across a functional component that has 6 different useEffect calls, the function ends up being 1k lines long, which isn't manageable. The same goes for finding a class componentWillReceiveProps that is super long too, the tool is only as good as the person using it.

Collapse
 
pensarfeo profile image
Pensarfeo

My disappointment with hooks lead me to write a small HOC that allows writing stateful components without functions, and with all the functionalities of classes.

github.com/Pensarfeo/react-makesta...

This is highly experimental, so I would appreciate some feedback on the project!

PS: No hooks were harm wile writing this package :)

Collapse
 
samsch_org profile image
Samuel Scheiderich

One of the most common struggles I've seen people have is not being able to remove the idea of imperative life cycles from their mental flow, and switching to thinking in terms of declarative effects.

It's really strongly ingrained in a lot of users to think more about mounting, updating, and unmounting instead of just rendering, and what (effect) you want to happen when you render, given some state.

Collapse
 
bendman profile image
Ben Duncan

I find this is the most unnatural part of hooks: the lifecycle paradigm fit the mental model of React/DOM interaction a lot better than hooks, and many integrations with non-React libraries require instantiating on mount and destroying on unmount.

Collapse
 
kentcdodds profile image
Kent C. Dodds

This is very true

Collapse
 
samsch_org profile image
Samuel Scheiderich

It's been a common riff the last few days, so I've written a post on it: dev.to/samsch_org/effects-are-not-...

Collapse
 
dperetti profile image
Dominique PERETTI • Edited
  • The semantics are terrible ( useState(false) 😱, useEffect() 🤔). It's not meaningful, and this is not a good sign. I've worked with many languages and I've never seen anything like that. We're not all React pundits / teachers. Many of us are fullstack devs having to deal with other languages, Kubernetes, etc and deliver real work, and with Hooks, more "Javascript fatigue" ensues.
  • They look quite simple in code examples, but they are not that much in real life. The complexity is just shifted to other places, and not necessarily in a good way. The Additional Hooks sections gives you a hint of the troubles.
  • I love functional programming, but I think they've been pushing it too far (HOC, I'm looking at you), and apparently they have no plan to calm down. I wonder where their hatred of classes comes from.
  • This way of pushing new features to the community is questionable. In the python community, anything like that would be discussed for months before being implemented in the language or framework.
Collapse
 
beggars profile image
Dwayne Charrington • Edited

I would love to know why the React team hates classes so much. It's a similar story with Vue, they're pushing anti-classes rhetoric when there is nothing wrong with classes. The arguments people have against them all mostly relate to inheritance, completing ignorant of the fact this is the reason we have decorators (for metaprogramming).

Angular and Aurelia devs have been building apps just fine with classes since 2015. It seems to me that React and now Vue is so against them because their designs predate ES classes and classes are a convenient scapegoat for outdated architecture. I have never seen a valid argument against classes in Javascript that didn't boil down to personal opinion or design differences.

Collapse
 
maxart2501 profile image
Massimo Artizzu

Your comment deserves more upvotes.

Collapse
 
dperetti profile image
Dominique PERETTI

It's just one day old :-)

Collapse
 
bigab profile image
Adam L Barrett • Edited

References in custom hooks

For me, one the hardest thing with hooks is about following the references when creating custom hooks.

I end up with a lot of useMemo()s and the "not recommended" useEventCallback technique and a lot of ref tracing of make sure it's not my custom hook causing unnecessary renders.

Example:

const useRelatedState = (
  items,
  defaultMapToState = () => {},
  identifier = v => v
) => {
  const [map, setMap] = useState(() => new Map());

  const tuplesWithRelatedState = useMemo(
    () =>
      items.map(item => {
        const id = identifier(item);
        return [item, map.has(id) ? map.get(id) : defaultMapToState(item)];
      }),
    [items, map, defaultMapToState, identifier]
  );

  const setStateForItem = useCallback(
    (item, newState) =>
      setMap(map => {
        const id = identifier(item);
        const currentState = map.has(id)
          ? map.get(id)
          : defaultMapToState(item);
        if (typeof newState === 'function') {
          newState = newState(currentState);
        }
        if (currentState !== newState) {
          return new Map([...map.entries()].concat([[id, newState]]));
        }
        return map;
      }),
    [setMap, identifier]
  );

  const ref = useRef();
  ref.current = useCallback(
    mapRelatedState => {
      const newStates = items.map(item =>
        mapRelatedState(item, map.get(identifier(item)))
      );
      if (
        items.length !== newStates.length ||
        items.some((item, i) => map.get(identifier(item)) !== newStates[i])
      ) {
        setMap(new Map(items.map((item, i) => [item, newStates[i]])));
      }
    },
    [items, map, setMap, identifier]
  );
  const setAllState = useCallback(
    mapRelatedState => {
      ref.current(mapRelatedState);
    },
    [ref]
  );

  return [tuplesWithRelatedState, setStateForItem, setAllState];
};

Async effect without use actions

Also, any async effects that change state but are not caused by a user action, such as loading a list of things when first rendering a component...

const AsyncDataList = () => {
  const [muppets = [], setMuppets] = useState();
  useEffect(() => {
    fetchMuppets().then(setMuppets);
  }, []);

  return (
    <ul>
      {muppets.map(muppet => {
        return <li key={muppet}>{muppet}</li>;
      })}
    </ul>
  );
};

...makes the component annoying to test because it currently throws up a warning about not being wrapped in act and currently act() is a synchronous call

describe('Test a loading component', () => {
  afterEach(cleanup);

  test('async data loading', async () => {
    const { getByText } = render(<AsyncDataList />);
    await wait(() => getByText('Kermit'));
    expect(getByText('Kermit')).toBeInTheDocument();
  });
});
/*
Warning: An update to AsyncDataList inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  * fire events that update state *
});
* assert on the output *

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act
*/

There is going to be a "fix in the next version", about the asynchronous part, but will it help with async effects that have no user action to initiate them?


Reset state based on props

Also I often find myself wishing I could "reset the state" based on a prop change. The only easy work around I've found that seems to work is just dispatching/calling a callback in useEffect that run when the prop changes.

const MyComponent = ({ theProp }) => {
  const [someState, dispatch] = useReducer(reducer, { initialState: true ));

  // 😕best idea I've had so far
  useEffect(()=> dispatch({type: 'reset'}), [theProp]);
}
Collapse
 
wolverineks profile image
Kevin Sullivan

If you want to reset all the state, you can assign that prop the key of the component, and it will rebuild that component in it's original state

Collapse
 
bigab profile image
Adam L Barrett

Right, yes thank you, I can push the problem up and reset with a key.

But you know, sometimes it’s not the only state, and it would be nice to solve the problem internally in the component rather than lift the problem up, but yes, great idea, thanks

Thread Thread
 
wolverineks profile image
Kevin Sullivan

Hmmm, interesting. My brain's not going to let this one go... Could you provide a concrete example? and I'll let you know what I come up with.

Thread Thread
 
bigab profile image
Adam L Barrett

Okay @wolverineks , here's a derived but concrete example:

Let's say you've got a component that lists the members of a team, and you can switch the teams and view the different members in the members-list.

In that list, each team member has a small UI state, to determine if the details view is showing, this doesn't seem appropriate to add to the team-member itself, because that object is provided from elsewhere (global/domain state), and it is really just a UI concern.

In the list of members, there is also a toggle with the option to "Hide Inactive Members", so you may not want to use the "reset with a key" method when the team changes, because you'd lose the "Hide Inactive Members" state.

So in the example above, you can look at the custom hook use-open-state.js and see that what I am doing is using useEffect to re-set the state for "what's open" whenever the list changes.

There are lot's of ways to solve this particular problem, keep a member mapping in state no matter what team shows, some crazy memoization, moving the "hide inactive members" state out of the members list (and use key to reset instead)... lot's of ways, it's not an insurmountable problem.

But my point was, I often find myself wishing useState() (or useReducer()) would take an inputs/deps argument like useEffect/useMemo/useCallback, so I could just reset my initial state based on a prop.

Something like...

useState( list.map(addExtraProp), [list] )

I think that would be swell.

Thread Thread
 
kentcdodds profile image
Kent C. Dodds

I think the simplest solution to this problem is to Lift State Up no?

Thread Thread
 
bigab profile image
Adam L Barrett

Well, personally I think useState() having a method of reseting state would be the 'simplest' solution...

useState(list.map(addExtraProp), [list]);

...but yes, I could lift the state out of the MembersList, and convert it to a fully controlled stateless component, and just reset the state in the handler for the Team's dropdown onChange. Definitely another good way to solve the problem.

I guess maybe I am the only one who ever felt like they wanted a way to reset the state in useState() or useReducer()

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

I guess this post is kinda old - but it popped up today in my Dev.to feed, so...

On a practical level, I've noticed that the "life cycle" of Hooks isn't completely analogous to class-based components. This has caused me some degree of headaches, and I've only recently begun using Hooks heavily. They say things in the docs like "this Hook is analogous to this lifecycle method", but, umm... not exactly. I know that part of this is just about learning the ins-and-outs of Hooks, but it can still be frustrating when you've pretty-much mastered class-based components.

On a more subjective level, I have a really hard time whenever I'm doing "Pattern X" and then someone comes along (like Dan Abramov) and says, "Yeah... that other way you're doing everything sucks - so you should do it instead with 'Pattern Y'." To be clear, I'm always open to reading/hearing about new innovations. But when I see the "innovation", I need to see some compelling reason to switch. And no, the reason can't simply be, "The Old Method is stooopid and the New Method is awesome - so use the new method." That kinda "reasoning" is a quick way to turn me off to the new concept.

I've now done a fair amount of work converting class-based components to function-based components with Hooks. For the vast majority of these "refurbished" components, when I'm done with the conversion, I sit back and look at the difference and think, "Yeah... it's basically the same thing, with just a modestly-different syntax." And by now, there are many blogs/tutorials/etc that purport to show you how your old, stodgy class-based components can be converted to Hooks. Inevitably, the author drones on about how much "better" the Hooks version is. They declare it to be "cleaner". And yet... it looks damn-near the same.

But like so many of the other elitist trends in the JavaScript community, once the fanboys decide that Pattern Y is just clearly superior to Pattern X, there's no reasoning with them. To be honest, I've kinda given up the theoretical fight, because I'm tired of someone (e.g., hiring managers) looking down their noses at my horrible, ugly, unconscionable code that uses class-based components. If I showed a lot of React devs how I've managed to delivered quantum computing in JavaScript - but I did it in (egads!) a class-based component, there are just too many fanboys out there who will scrunch up their face and look at my revolutionary code as though it's the digital equivalent of a fart.

FWIW, I wrote an entire post about the odd demonization of the class keyword in JavaScript:

dev.to/bytebodger/the-class-boogey...

Another thing that I don't particularly like about Hooks is that they feel very much like an all-or-nothing proposition. Yes, I know that you can use functional, Hook-based components in the same codebase with old-fashioned class-based components, but I've already seen that run into a series of headaches that I would've never had to deal with if the Hooks Cartel hadn't decided that "Classes are bad... mmmkay???"

Collapse
 
dance2die profile image
Sung M. Kim • Edited

The part that got me (reminded me of because of this comment) that hooks don't share "states", but it's for sharing "logics".

Another one being useEffect. I still refer back to A Complte Guide to useEffect often.
useEffect still has clicked 100% especially on parts with getting new fresh value (using ref).

Collapse
 
freddiecarthy profile image
Freddie Carthy

Shifting the way I think about components, from lifecycles to instances, especially when converting large components that rely on those lifecycles.
The end result is always clearer and easier to read code. Getting there requires some thought.

Collapse
 
kentcdodds profile image
Kent C. Dodds

Could you clarify what you mean by "instances?"

Collapse
 
freddiecarthy profile image
Freddie Carthy

What I mean is, rather than thinking “should this component update?” I instead can shift my thinking to “my props have changed, how should this instance of my component react to them?”

Collapse
 
mikevelazcomtz profile image
Miguel Angel Velazco

I hate how useEffect works.

I'd prefer having lifecycle back.

My main reason?

Is a hook that has many concerns. I prefer to think of functions as things that does only one thing right and nothing else.

I've tried a PoC for the project I'm working on and useEffect had very tricky behaviors at first.

Just think a bit about it:

If you want to do something on every render:

useEffect(<function>)

If you want to trigger something based on change of only one variable:

useEffect(<function>, [variableToWatch])

If you want to trigger on mount:

useEffect(<function>, [])

If you want to trigger something on unmount:

useEffect(
  () => {
    return  () => {

      // Some cleanup code here
    }
}, []);

And what about cleanup functions?

Do you think they behave the same on all the cases?

Oh, my dear friend let me tell you that you're totally wrong.

As I said before, this works on component unmount:

useEffect(
  () => {
    return  () => {

      // Some cleanup code here
    }
}, []);

And being honest I don't know when/why is the cleanup function is called here:

useEffect(
  () => {
    return  () => {

      // Some cleanup code here
    }
};

That's my two cents about it.

Collapse
 
nhidtran profile image
nhidtran

I've been able to keep state closer to the component but I'm not sure if that's a great paradigm? Not sure. Maybe I am using hooks incorrectly, but I am making smaller api calls in the component than at a HOC and being able to reuse that data like when using redux. Is that good? Also, having trouble figuring out using hooks to its highest ability. Right now, it's nice to useState instead of having to care about this.state or this.setState or this in general but how to design components truly reusable. I saw a good example with an infinteScroll example hook but thinking about it on my own is difficult.
There are also no best practices (I think) so refactoring class components to functional, I don't know the benefits aside of it being more modular

Collapse
 
roggc profile image
roggc

I like using hooks. I don't like too much using classes. I have learned React from the beginning using hooks, not classes. The most interesting thing about useEffect is how it can pass from two renders to infinite loop changing only a little bit the second dependency parameter. Or for example, if you use useReducer and dispatch an action, you must know that the effect of this dispatch will not be visible until the next render, so you have to put your code inside useEffect in order to take account for this dispatch. So you end up using a lot of useEffect. Sometimes it's difficult to tune the second argument for useEffect. As a project I made myself the minesweeper game using hooks. It was cool to do that and I learned quite a lot. Or sometimes doing little test projects with a lot of console.logs to see what is going on with renders. With time we can master it!