Hacker News new | past | comments | ask | show | jobs | submit login
A guide to useEffect in React (overreacted.io)
97 points by feross 13 days ago | hide | past | web | favorite | 75 comments





React hooks solve a problem most people don't have. Classes worked fine in 99% cases and have the nice benefit of encapsulating any side effects. Hooks make it easy to hide side effects and dependence on state (think global variables) by letting developers hide these things several layers deep in functions that may get called by components. This is a footgun waiting to go off.

If you need to use state or side effects, just use classes. Most of the time all you need is pure components with fewer stateful components above controlling the pure components. Pass down your state and state modifiers through explicit and easy to follow props (or use a state management system if you find yourself prop drilling). If you're using state and side effects everywhere, you're probably not using React correctly.


Hooks solve the problem that state in classes is not compositional. If you have a stateful class that manages two or more bits of state, it's a lot of work to move one bit to a different component: if not already you have to make the other component stateful, move code over there, check that all the lifecycle methods are updated etc. With hooks you can copy&paste one or two lines of code to another component and be done.

I agree that the bulk of your application state should be managed centrally. But I often need small bits of state all over the place, not related to the overall app state but for UX purposes. For those cases hooks are much more pleasant to use that writing stateful classes.


> it's a lot of work

It should be a lot of work. There should be a penalty for this. State makes your code harder to reason about and should be avoided (i.e. contained) as much as possible.

> But I often need small bits of state all over the place

If you need small bits of state all over the place you may need to rethink things or use a state management system.

The spirit of React hooks might be to make things easier for certain edge cases but instead they will be abused by those who don't understand them to make their code harder to understand. Trust me, I've already started running into this with those who decided to cargo cult React hooks.


While it is true that you should not overuse state, it is almost impossible in real-world applications not to have it. I.e. a single UI toggle that shows/hides some content requires a field on the components state. Putting this in the props or even a state management container like redux would be overkill.

I have built real world applications with React (consumer, insurance, B2B, I work at Facebook using React currently). I've handled some pretty crazy edge cases. I've never felt I needed something like hooks. The example you gave me sounds just fine for a "smart/dumb" component pattern[0]. If you need something between props and redux, there's plenty of existing options that work fine. React context and the render props pattern come to mind.

0. https://medium.com/@dan_abramov/smart-and-dumb-components-7c... (The author of this blog post pushes hooks now yet the pattern works well in most cases)


When you use TypeScript with React (and want sound typings per policy so any type erasures/widenings are considered bad practice) Hooks are a big leap forward.

Typing classes was much to verbose, and the type system of TypeScript to this day is not a perfect fit to type class-based components. Hooks solve this problem - probably unintentionaly.

Example: With Hooks you almost never need to explicitly type the type of state of a component, as whatever you pass to useState() will auto-infer the type. While when using class-based Components, you'd have to specify the state type as a generic argument; you'd have to manually specify it when you set a state using this.setState() as partial types were allowed due to reacts auto-merging of the passed argument to the state. And you'd have to use some kind of Partial<State> type when assigning only some values of the state in the property constructor.

Similar issues exist when using the static defaultProps field in TypeScript - the type couldn't be inferred from the generic class argument, as a Partial<Props> type is valid for the defaultProps field. As a result, the defaultProps (as well as the default state property) is cast as the empty object type {} by default if you don't explicitly assign a type - which makes you lose basically all the type information because every object is assignable to {} in TS.

And if you ever tried to strictly type your HOCs or renderProps you know how hard it was, sometimes utilizing the conditional/mapped generic types feature of TS. This is completely gone with Hook.

I could go on telling how stupid it is when you disable nullable types in TS (strict mode) because TS will not let you use any non-nullable prop inside of render() - even if that prop is initialized in the static defaultProps field. You'd have to override the non-null check using TS exclamation point operator. This is also gone with Hooks.

I don't know if they designed the Hooks API with TS in mind or if this is completely accidental, but if you migrate a TS project with strict mode enabled to Hooks, you will see how much implicit type annotations can be dropped, and still retain strict type checks. Big fan of it.


if you migrate a TS project with strict mode enabled to Hooks, you will see how much implicit type annotations can be dropped

I think you meant "explicit"

I agree with you. I've not been too bothered by typing state and default props, but higher-order-components are almost impossible to type properly (a good typedef doubles the LOC count).

I don't like that hooks don't abstract away the shared behaviour as well as HOCs (the component is unaware of the HOC while it it aware of the hook), but being explicit isn't a bad thing either and it's definitely worth it if it makes typing a non-problem


I wouldn’t say components are unaware of HoCs.. they are implicitly based on the props they accept.

Of course, but they don't know if the props come from a HOC or another "normal" component. If you're updating the render() of a component, you don't care where the props come from.

It's not that important TBH, the abstraction is pretty thin


> With Hooks you almost never need to explicitly type the type of state of a component [...] While when using class-based Components, you'd have to specify the state type as a generic argument

You don't need to do this. You can accomplish the same thing with class syntax. Here's an example:

  class MyComponent extends Component<Props> {
    state = {
      inferredNum: 1, // TS will infer this to be a number
      explicitNullableString: null as string | null, // Tell TS this will be either null or a string
    }
    ...
 
> And you'd have to use some kind of Partial<State> type when assigning only some values of the state in the property constructor.

I can't say I've ever run into this problem with TS and React but maybe I'm not understanding what you're saying? Do you have an example?

> Similar issues exist when using the static defaultProps field in TypeScript

Here's what I do:

  defaultProps: Partial<Props> = {...}
That doesn't seem such a big deal?

> And if you ever tried to strictly type your HOCs or renderProps you know how hard it was, sometimes utilizing the conditional/mapped generic types feature of TS. This is completely gone with Hook.

It wasn't hard to me but I can understand why others don't like generics. Hooks have their own learning curve, so you're trading one thing for another.

> You'd have to override the non-null check using TS exclamation point operator.

This has been fixed with TS 3 I believe.


I'm writing TS components daily, I'm not sure why, but I never encountered any of the issues you mentioned.

I can only agree that typing HOC was hard when I did it the first time.


Then let me ask you: How do you type .defaultProps? How do you type the "state" property on the class? If you did explicitly repeat the type already specified in the Generic Type Argument: Component<IProps, IState> you would know what I mean with too verbose (you have to repeat a type that could be infered, but due to TS restrictions isn't able to). If you did not repeat the type for those fields, you lost typesafety without noticing it. You can then go ahead and assign { foo: "bar" } to state (or use it in setState) and will not get a compilation error (which you shold as foo is not a known property for your state).

And I sure as hell bet you do not use "strict" compilation in TS, which opens typing issues everywhere.


We are using strict mode.

If I do `setState({ undefinedStateKey: 123});` it warns me. Just checked. It may be linting not TypeScript. We have IState defined only as Generic Type Argument.

We define defaultProps as static in class. And you are right defaultProps are not being type-checked against Generic Type Argument IProps.


Well if you are in strict mode, it should break the build with an error, not a warning. This seems to be a linting rule.

But you see the issue with defaultProps and state here. You can basically assign anything to defaultProps or use in setState() even so you were explicit of the props type in the Generic Type Argument.


Sometimes I need to create the props at a different level/location and not directly on the component. The interface for the (class component) props is then very useful.

This is no different when using Hooks. You specify the type of props as the type of the first argument in the functional component:

  function MyComponent(props: IMyComponentProps) { ... }
Where the type definition of IMyComponentProps resides doesn't matter, you can import it from anywhere.

I was simply pointing out that explicitly defining an interface is not necessarily a downside.

I’ve been using React for almost 4 years straight - I couldn’t disagree more. Hooks are incredibly useful in removing things that pollute the component tree like higher order components or components with render props which are hard to visually parse. If you have a common state pattern that multiple components use, there’s no good way to share that pattern without one of the solutions I just mentioned.. and hooks are by far the cleanest way to do that. Avoiding prop drilling is so easy with something like useContext.

And hooks are always called.. there is no “maybe” when using them.


I've had way more random problems from not binding class members correctly than I have with useEffect. Hooks have allowed me to rip out a bunch of JS class boilerplate and my code is cleaner and easier to read as a result.

Are you against using Babel and the class properties proposal? Binding members shouldn't have been a problem in your codebase in recent years.

I use Typescript. I'm not interested in adding another processing/parsing tool to the stack I already have. Hooks are a much more elegant solution to state management than classes, IMO, but it will probably take some time for best practices to emerge, just as they did for classes.

TypeScript already supports those 'class properties' - and with a few extra features - so you don't need to use Babel for this syntax: you already have it.

I'd still rather use hooks though :)


React hooks are just a fancy (perhaps unnecessary) way of getting people to embrace closures, which help you avoid other common footguns when dealing with temporal data. If you understand closures and you know how to use them to "remember environments", then React hooks are little more than tools for concision.

Other than e.g. useEffect returning a function that closes around something within, I don't see what hooks have to do with closures.

You don't need hooks to do any of this, but hooks have everything to do with closures. Hooks are created every single render, unconditionally, guaranteeing that each hook is one-to-one with the render environment that created it. The point of using hooks is that you are always referencing the props and state at the time of creating the hook and not at the time of running the hook. In this way, hooks become part of your rendered output.

I know how hooks work, but what you describe has nothing to do with closures.

I feel the same way.

And how isn't Hooks are worse off in terms of testability? I have got into too far with it, but it seems to favor small piece of ad-hoc code over classes, which seems neet from the surface, but doesn't feel very encouraging towards tests coverage.

Anyone that applies Hooks to production project can share your experience so far? Would love to see some real world adaptation success stories before making the move.


> it seems to favor small piece of ad-hoc code over classes

I think that it's important to realize is that classes aren't the only alternative to small pieces of ad-hoc code. Hooks are composable via functions. If you need to reuse your stateful behavior and side effects you can factor them into functions, and if you don't, you'll have exactly the same problems with classes.


I agree 100%. I think hooks turn pure components into their impure cousins which will eventually make following state changes a nightmare.

Hooks solve some problems nicely, the main killer for me is that they don't work with classes, so you end up with 2 wildly different paradigms in your codebase.

Feels like hooks should have been a clean start in a new framework rather than retrofitted into React.


I have a hard time grasping this concern. How your codebase ends up depends on how you set it up. The introduction of hooks doesn't magically replace half of your class components with functions using hooks. You can introduce them when and how you want to introduce them.

On the other hand, people who are fine with having state managed both via class components and via hooks (and why shouldn't they be?) can gradually introduce the concept and refactor existing components as needed, if and when they see some advantage in using hooks at all.

If you don't want milk with your coffee, it's not a problem that there's a pitcher of milk at the table.


Custom hooks encapsulate logic so you can no longer use it in all of your app

This small library [1] let's you use hooks in classes. Probably has some bugs but may be good for a transition period

https://github.com/kesne/with-react-hooks


For me the problem hook solves is the heartache pain I feel when I have to convert a SFC to a React.Component. Adding useState and useEffect feels much more natural. I don't think it's any more or less bug-prone than React.Component, especially because you have to be careful about making sure componentDidMount() and componentDidUpdate() are in sync.

but you introduce the danger of relying on an api that determines your state based on the order of function calls. if you accidentally take an api that pretends to work based on call site and use it in a loop or callback everything falls apart.

It only relies on that order between renders. It’s really a non issue.

I just spend the last month building a new chunk of our app in hooks to try them out, and it’s really nice to be able to wrap up shared stateful logic without HoCs.

A bunch of the HoCs in this app take a “propKey” param, because the HoCs are all stacked on top of each other, and prop collisions happened frequently.

Also, it’s a gigantic pain to use TypeScript and “compose”.


it’s really nice to be able to wrap up shared stateful logic without HoCs

agreed. I loved HOCs for the composibility, abused them and very rarely encountered prop collision, but TS (or Flow) made them a massive pain


Only the order of the hook function calls is tracked. The "danger" of 3rd party APIs is a theoretical one, as in most cases you would not put hook function anywhere else except the first level inside the functional component - not into callbacks or whatnot of 3rd party libraries.

IIRC this is easily preventable using the ESLint rule.

They solve the problem of me having to use classes at all.

In a way, they are merely syntactical sugar.

I agree with the parent poster. Before, you had functional stateless components. Now you have components that look functional and stateless, but are in fact stateful (and impure).


IMO React state managers are an anti-pattern; they were somwhat useful before the final context api was introduced, but even back then simply passing down props is almost always the better choice, even when you're passing down multiple levels.

With Typescript, it's a non-issue to pass the props down to the tree.

When do you actually need to use hooks? I saw a nice visual where hooks ended up reducing code, but I am not sure I grok it.

I've been using React in projects large and small for years and I can remember VERY FEW situations where I said to myself "gee I wish I could bundle a bunch of state or side effects together and share it between components like React mixins of lore". If you can't think of a situation where you need it, that's probably because you don't. Anything plain old JavaScript class components can't handle here were solved by using HOCs/render prop patterns or React context (often in the form of state management libraries such as Redux).

> HOCs/render prop

Both of these inflate the VDOM, and present themselves as a false hierarchy that your app requires. HOCs artificially inflate the props that any component requires, and it's harder to see what props you're actually supposed to pass to the component, vs the ones that should come from a HOC. Hooks remove a lot of these anti-patterns.


When you want to work with state in a component (so most of the time) but are too stubborn to admit FP isn't the right choice for the job

This is my feeling too to be honest. I think hooks are very misguided. Unfortunately it's no longer FP anymore when your "referential transparency" has been completely blown away.

FP?

Functional Programming

Having used React hooks for the past couple of months, I think I've come to the conclusion that I'm not going to recommend any body to switch away from class components.

I understand that hooks solve the problem of being able to compose side-effects and lifecycle in your components, but honestly, in the last three years of using React, I don't think I've ever actually had a scenario where hooks would have been a great solution for me. I'm not sure if this means I've been designing my React components very well, or very badly or what.

While hooks look like they might be good at some things (ex: useState is awesome), I think they come with too many subtle gotchas (the amount of literature on handling these gotchas, that's been written in the few months of hooks being in beta is mind-blowing) that I'd much rather just use class components.


That's ultimately it isn't it. I think Hooks are great and I've been using them and would use them for everything. I'm also a Dev manager, and understanding those gotcha's it isn't even about not knowing which would be an obstacle for less experienced developers, but how easily you repeatedly accidentally fall into them.

It's hard. I feel like they are 90% right, but unless you are not only a very competent developer but also insanely careful you will make many mistakes.

I perhaps am a bit more torn as I have experience with fine grained libraries like Knockout and MobX and I even more so see the benefit of the potential here. I think the idea of it is a better, more clear way to solve problems (from previous experience). But the number of times I've missed [], or forgot to close over state properly even knowing. I usually catch myself but I can only picture the team biting into this.


What gotchas are you referring to besides exhaustively listing dependencies and implementing something like `setInterval` via hooks?

I rewrote my side project (https://robojs.com) from Typescript/Redux/React (class based components) to Typescript/Mobx/React (hooks) in less than a week. The process was really simple (mostly deleting code). In the end the source code is so much smaller (at least 50%), simpler, easier to read and reason about, largely because of Mobx, however hooks is so much nicer to work with compared to class components, and I doubt that I would ever want to go back.

> While you can useEffect(fn, []), it’s not an exact equivalent. Unlike componentDidMount, it will capture props and state. So even inside the callbacks, you’ll see the initial props and state.

In my experience this is a fairly common source of confusion. People have a tendency to write things like:

   useEffect(() => {
      const unsub = subscribeToThing(thing => handleThing(props.id, thing))
      return () => unsub()
   }, [])
This looks pretty good except it's broken if props.id changes. handleThing() will always be called with the first props.id.

I'm the defacto React tutor at my company and issues like this make me pause before recommending new React devs to use hooks. useState is awesome and feels more natural to people than setState(). useEffect seems similarly simple but the subtleties can catch you.


The lint rule Dan mentions (eslint-plugin-react-hooks/exhaustive-deps) will catch errors like this.

It’s also worth noting that most class components people write don’t handle all prop updates correctly – Hooks tend to be slightly harder at first but tend to have fewer pitfalls in edge cases, especially if you stick to the rules (which there are lint rules to enforce).


But there's something to be said about needing a lint rule for the API to be safely used.

The nice thing about functional programming is that you can reason about execution and write lint rules to fix pitfalls that you can't detect with imperative programming.

I think the article touches on that point. Specifically, the lint rule points out a problem that a class implementation would also likely have. Except that you can’t easily lint the class implementation in a similar way.

The lint rule is not to appease React. It’s to show you where a component doesn’t handle updates. This is already very common with classes but those issues tend to stay unnoticed.


To play devil's advocate, this has always been the case with React. Newbies always mutate props or use setState incorrectly or in the wrong lifecycle methods. We've relied on linting to solve this for a long time now.

Unfortunately we use tslint so we'd have to add eslint just for this.

> It’s also worth noting that most class components people write don’t handle all prop updates correctly

I'm very guilty of this however I'm usually being intentionally lazy and assuming that a certain prop won't ever update. With useEffect it usually happens by mistake.


> Unfortunately we use tslint so we'd have to add eslint just for this.

This won't be a problem forever (https://medium.com/palantir/tslint-in-2019-1a144c2317a9) :).


I'd be wary to encourage useState. It seems too easy to accidentally use in loops/callbacks/etc where your state would then break because react tracks it via call order instead of call site.

The rule about unconditional calls tends to sound weird (I personally found it very strange when I first saw it), but in my experience, people do not have a hard time following it – it’s very rare to want a variable number of state fields, for example.

If you do end up making a mistake, React has both lint rules provided and runtime checks to catch any errors. But from watching many people learn and use Hooks, we haven’t seen it to be a problem in practice.


You don't use useState to set the state, you use useState() to GET back a function that can mutate the state. And this function is safe to call in a loop or conditional.

When using hooks, yes, you have to remember not to call any use* function inside loops or conditionals (or use the linter plugins for that). Besides that, hardly any room for errors.


I've toyed with hooks for a few hours and, even if I understand why they're focusing on teaching the basic pitfalls (dependencies arrays mostly), I don't think they're the most confusing part (as long as you know how closures work, i.e. as long as you know JS).

The real deal is `useEffect` vs `useLayoutEffect`. It seems easy, but it's not. When should I use each? According to the docs, use `Layout` if you interact with the DOM for measurement or whatever (I mean, look at its name)... WRONG!

I made a custom `useFetch` hook that uses `useEffect` to re-fetch when the URL changes. It should be fine, right? Every other `useFetch` I've seen on GitHub does that too... And it's wrong.

I noticed that as soon as I added a second `useFetch` that fetches depending on the first's data, there is flashing in the UI. Since `useEffect` is asynchronous, there's a small gap where both internal states can be desynchronized (completely unrelated to the promise, I mean the internal `isLoading`, etc.) `useLayoutEffect` solves the issue (since it blocks the rendering until both effects are finished updating their internal states) but then... my internal hook implementation affects the external behavior in non-obvious ways!

I don't know whether my hook consumer needs to block the UI until all updates finish, so I actually need to make two different `useFetch` and `useSyncFetch` hooks, or just ditch the async one.

It's definitely a footgun. I predict this is going to bite everyone's ass. It's also not really well understood (try searching for "useEffect vs useLayoutEffect") and the React team is completely ignoring the issue. I haven't seen it mentioned anywhere, and I can't believe I'm the only one that noticed it (I guess hooks haven't been used that much yet?)

The issue is exacerbated by `useEffect` being the "default" in the docs, while `useLayoutEffect` is explained later as an option (even if they admit that it's the safest option if you're migrating from classes).

I recommend sticking to `useLayoutEffect` unless you're absolutely 100% sure your consumer does not need to block the UI (when can you be 100% sure?)

There's a similar thing with `useState` vs `useRef` for mutable state (where the only difference is that `useState` triggers re-renders) but since `useState` is presented as the default (re-render even if you don't know if for consumer needs to) it does not seem like that much of a footgun (you have to reach for `useRef` if you really need it, e.g. in the infamous `useInterval` Abramov's blog post). It's also very similar to how state vs. instance variables worked, and it's very obvious how `useState` interacts with React (completely different behavior unlike `useLayoutEffect`, which is the same but in different parts of the component lifecycle).

When your internal hook implementation affects your component's behavior in non-obvious ways, there's something wrong with the hooks API. As proof, see how many custom `useFetch` hooks use `useLayoutEffect` when they should. Zero. And I'm sure there are many more instances of this problem hidden in other custom hooks (it's hard to tell, since it's not obvious until you use a hook that depends on other hook's output and see the flashing).


Do you want to file an issue? I’d be happy to answer some of these questions and maybe improve the docs.

useLayoutEffect is not the recommended way to do it. You’re probably missing a simpler solution like setState during render. But I’d need to see the code to be sure, and GitHub issue would be a better place to do it.


Definitely, it crossed my mind, I just have not had enough time to write an minimal bug example unfortunately :/

In this case I think the problem is `setState` is inside my `useEffect` and I'm using its dependencies array as a sort of substitute to `componentWillUpdate` + compare `props` to `prevProps` (hence the async state update).

In class-based react I would just atomically `setState` both states and there would be no problem, but since hooks allow you to modularize behavior a new type of data dependency appears (there are multiple states in a single component) making it harder to reason.

Thanks for chiming in, will file an issue ASAP.


No problem, I'm sorry for the confusion. I think your use case might be solved by this approach:

https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-g...


I haven't used this pattern very extensively yet myself, so definitely take this with a grain of salt, but I think a good way to avoid the pitfalls around accidentally omitting depencencies consistently might be to define our effect functions as pure functions _outside the scope of the render function_, that takes a list of dependencies from our render function, and then returns an effectful function to pass into useEffect.

Since these functions don't even have access to the render function's closure to begin with, we can eliminate the possibility of our effect functions ever lying about their dependencies, as they have no choice but to receive their dependencies explicitly as arguments.

Probably easier to demonstrate with a code sample:

```

  const effectFn = (dependency1, dependency2) => () => console.log(dependency1, dependency2)

  const Component = ({prop1, prop2}) => {
    const dependencies = [prop1, prop2]
    useEffect(effectFn(...dependencies), dependencies)

    // rest of render function...
  }
```

We can even define a separate hook to solidify this pattern and make it less cumbersome to write. I whipped up this quick example a while ago to show coworkers and tentatively named it `useEffectFromArgs`:

https://codesandbox.io/s/4wwqnqp3zx

For those who don't want to click the link, it's just a really thin wrapper around useEffect:

```

  const useEffectFromArgs = (effectFromArgs, args) => 
    useEffect(effectFromArgs(...args), args)

  const effectFn = (dependency1, dependency2) => () => console.log(dependency1, dependency2)

  const Component = ({prop1, prop2}) => {
    useEffectFromArgs(effectFn, [prop1, prop2])
    // rest of render function...
  }
```

I actually came up with this originally because I wanted a way to be able to test effect functions in isolation as pure functions rather than involve the entire React rendering pipeline just to test something that could have been tested as a pure function, which are by far the simplest tests to write and reason about, and the fastest tests to run. So for me, avoiding the accidental scope capture was really just a nice side effect.

The pattern is equally applicable to other hooks like useCallback, which was actually the original motivation for this. I also have another version of this that use named arguments to specify dependencies rather than a list, that I prefer using over the other one because positional semantics don't scale: https://codesandbox.io/s/4x86wy3r60

It does make me wonder why the hooks API doesn't just work like this to begin with though. I could definitely be missing some downsides to this approach, so if anyone wants to chime in with any critiques/insights, I'd love to hear them.


Maybe I'm not the target audience, but I have no clue what "useEffect" does or why it is important. An introduction on the subject (including which language is being discussed) would be really helpful.

React 16.8 introduced a new concept of hooks, "useEffect" being a built in one. Hooks are extremely useful for streamlining and reusing React component code. "useEffect" in particular can supplant the "componentDidMount" method which among other things is common for fetching data into a component. There are some gotchas to be aware of when using "useEffect" and this article is explaining them.

There's a link in the first sentence of the article. It would have taken you less time reading it than it probably took you to write this comment.

Also "This article assumes that you’re somewhat familiar with useEffect API." with another link a little further down.

In the JavaScript UI library React, useEffect is a hook (recent addition to the library) that allows you to perform side effects in function components. https://reactjs.org/docs/hooks-effect.html



Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: