A guide to useEffect in React
overreacted.io
overreacted.io
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.
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 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.
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)
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.
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
It's not that important TBH, the abstraction is pretty thin
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 can only agree that typing HOC was hard when I did it the first time.
And I sure as hell bet you do not use "strict" compilation in TS, which opens typing issues everywhere.
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.
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.
function MyComponent(props: IMyComponentProps) { ... }
Where the type definition of IMyComponentProps resides doesn't matter, you can import it from anywhere.And hooks are always called.. there is no “maybe” when using them.
I'd still rather use hooks though :)
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.
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.
Feels like hooks should have been a clean start in a new framework rather than retrofitted into React.
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.
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”.
agreed. I loved HOCs for the composibility, abused them and very rarely encountered prop collision, but TS (or Flow) made them a massive pain
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).
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.
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.
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.
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.
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).
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.
> 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.
This won't be a problem forever (https://medium.com/palantir/tslint-in-2019-1a144c2317a9) :).
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.
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.
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).
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.
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.
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-g...
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.