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

Oldest comments (66)

Collapse
 
xiel profile image
Felix Leupold

Accepting not being able to exit early/to use conditions around useEffect. It took a few days to get used to it, but normally you can just move the condition inside the effect.

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
 
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
 
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
 
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
 
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
 
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
 
seanmclem profile image
Seanmclem

The set-function of. useState is a little over-simplified for me sometimes. Especially with form fields

Collapse
 
kentcdodds profile image
Kent C. Dodds

Interesting. Could you clarify what you mean?

Collapse
 
seanmclem profile image
Seanmclem

When I use useState for a form field I need to update more states to track value, error, overall form. Instead of updating one json object. Idk now that I think about it, maybe it's the same thing. I like hooks more and more but I'm still in transition

Thread Thread
 
fnky profile image
Christian Petersen

This is an excellent use case for useReducer, instead of useState. With that you also make it clearer what state you’re handling, and the actions that can be used to update that state.

Thread Thread
 
seanmclem profile image
Seanmclem

Thanks. I'll look into it

Thread Thread
 
bigab profile image
Adam L Barrett

I don't know if useReducer would help all that much TBH, but...

You know, the setState() function returned from useState() can be passed a function, to update the state, which receives the current state and you are expected to return the new state.

setState( state => ({ ...state, updateValue: true }) );

This can be great when dealing with forms.

const [formState, setFormState] = useState({
    name: '',
    age: null,
    level: 1
});

const handleChange = ({ target: { name, value } }) => {
    return setFormState( state => ({ ...state, [name]: value }) );
}

return (
    <form>
        <input name="name" onChange={handleChange} />
        <input name="age" onChange={handleChange} type="number" />
        <select name="level" onChange={handleChange}>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>
    </form>
  );

Here's that code a little expanded:

I feel useReducer() doesn't add all that much to state hooks, other than being familiar to redux users. I share some of the reasons for this point of view in this talk if you are interested:
youtube.com/watch?v=gRXgE2iHRek

Collapse
 
evolutionxbox profile image
Jonathan Cousins

The exposure of foot-guns upfront. Thinking about rendering performance almost immediately (I know, I know... measure first)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
kentcdodds profile image
Kent C. Dodds

Definitely don't ignore eslint here. 99% if the time if you do you'll have a bug.

In this case the bug is: what happens when the URL changes?

Collapse
 
lamine105 profile image
lamine • Edited

The use practical use case of some built-in hooks is not clear (useLayoutEffect for instance).

Another pain point is testing hooks

Collapse
 
kentcdodds profile image
Kent C. Dodds

Have you seen this yet? kentcdodds.com/blog/useeffect-vs-u...

Also, for testing, if you write your tests well, then they shouldn't have to change between classes and hooks: kentcdodds.com/blog/react-hooks-wh...

Collapse
 
hoverbaum profile image
Hendrik

The hardest part for us has been a difficulting in structuring components around state management and testability.

Components with hooks make testing super hard for us (in a React Native context). We can not use mounting for testing, as we are in react native, and shallowly rendering a component does not run anything inside a useEffect hook.

Thus we had to resort to mocking our useEffect to test our code which is super fragile since it basically mocks out react itself.

Our best option seem to be to go for custom hooks only which we can mock out easily for component testing and mock useEffect for those. But that just leads to weird code where instead of gaining colocation and readability from hooks we get messiness.

So, we are largely sticking to classes. Little though we want to.

Collapse
 
kentcdodds profile image
Kent C. Dodds • Edited

Oh man, have I got good news for you: testing-library.com/docs/native-te...

Collapse
 
theodesp profile image
Theofanis Despoudis

To understand the differences between useEffect vs useRef vs useCallback vs useMemo. The other ones like useState or useReducer are easier.

Collapse
 
monfernape profile image
Usman Khalil

I'm stuck there too. I've read hundreds of times on documentation but it just doesn't click on my mind.

Collapse
 
jaybaldha profile image
Jay Baldha

Hi I am totally new to react. Can you explain what are the benefits of hooks over class in layman terms.

Collapse
 
kentcdodds profile image
Kent C. Dodds

Watch the video on the first page of the hooks docs: reactjs.org/hooks

Collapse
 
mmlumba profile image
Marian

Whenever I've worked with useEffect, sometimes I want certain effects to only run on mount or on update and useEffect runs on all state changes. I know there are ways to run effects only on mount (or update), but I do miss using the componentDidMount and componentDidUpdate lifecycle functions for easier readability.

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
 
maxart2501 profile image
Massimo Artizzu

Your comment deserves more upvotes.

Collapse
 
dperetti profile image
Dominique PERETTI

It's just one day old :-)

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.