Using useEffect Effectively
Watch on YouTube (React Conferences by GitNation)
What is this talk about?
Check out FAQ on this content, shortened version and extra resources for this talk at (From the video description.)
“We have used effects, so I remember I learned about useEffect in 2018 and I was very excited to use it.”
“When this happens in part of your app breaks, it's basically React, telling you shouldn't be using this effect inside of useEffect.”
From the opening of the talk.
Key takeaways
- We have used effects, so I remember I learned about useEffect in 2018 and I was very excited to use it. 1:00
- The real mental model is you want to put your fire and forget your action effects as close as possible to the events that would have caused the effect. 9:00
- You don't need useEffect for fetching data, like we talked about at the beginning, that was one of the big reasons that we used useEffect. 17:00
- UseEffect is for synchronization, activity effects go and useEffects action effects go in event handlers. 23:00
Quoted from the transcript.
Full transcript
From YouTube auto-captions. Punctuation and capitals were restored automatically. Technical terms were normalized; some errors remain. 4,198 words.
0:00 Introduction
Hello everyone, my name is David Khourshid and my sites will be showing up shortly. I'm sure. Oh yeah, there it is, okay. David Khourshid I'm at David K. Piano pretty much everywhere. I'm very excited to be in London again. I work at Statelite..ai, where we think a lot about state logic and effects. But enough about me. If I keep going, then this talk is going to last longer than your next prime minister. So let's get on with it. Okay, so, of course, I work at Stately in one of our engineers, Matisse. You might have seen him, he's in your npm, your node modules. I guarantee it, he's one of the most talented developers I know. Yesterday he created the PR where he asked, and my 100 confident in this fixed hell. No, it's based on useEffects, so we're going to be talking about that today. Our favorite hook useEffects.
But let's start from the beginning. Who remembers class components, right? So, yeah, some people really want them back, you know? So do I? All right. So, in this example, I. I remember that, you know, when React came out, I was very excited for it. I'm like, Wow, this is going to change everything, and reacts did change everything on every single render. So over here, we're doing what, what most components do, or what most React developers do, which is fetch data and show it on the screen. Admit, that's 90 of your job. But, you know, we had a nice, convenient, little like life cycle hook component did mounts to do that in. But now things are different. We have used effects, so I remember I learned about useEffect in 2018 and I was very excited to use it.
2:00 The hook specifically is for synchronization with activity effects…
Because I'm like, Wow, this makes things a lot simpler. So instead of making these big, bulky classes, we have these hooks that we could put things in. I wanted to fetch data, it was 2018, so I wanted to use new JS syntax and it was an effect, so I put it in useEffect. Try this reacts yelled at me, it said, listen, this async await stuff, it's fancy, but you cannot use it like that. You, you have to, you know, we. It doesn't infer that. Hey, I don't know what to do. With this promise that you return from useEffects, so we had to do things like it was 2015 and use dot then. So I fed some data, then I set the data. I'm like, this is simple, I just put it in the useEffect, I get my data. I'm good to go right wrong. Guess what happened to my AWS field the next day?
Like, yeah, so this, by the way, it will keep fetching and fetching and fetching, and it really won't. You know, React won't tell you that, hey, this is gonna occur as an infinite loop, all right. So at this point, I'm like, Okay, maybe I should read the documentation a little bit. So I scrolled all the way down. And so there was a tip. It's just a tip, you know? This very crucial bit of information is a tip in the current React Ops docs, optimizing performance by skipping effects. And so I learned that you're supposed to use this little dependency array, and since this didn't really have any dependencies, you just put it empty like that. If you look at it sideways, it sort of looks like an agonized React developer screaming into the void. So yeah, all right. So I learned my lesson, I learned what the dependency array is for, and so everything was good, right? Well, now I discovered that we have something called a race condition.
Because when I fetch something with an ID, and then I decide, you know what, I want to fetch something with a different idea. If that idea is cast, then that promise is going to resolve first. While the first one is still going. Because we didn't cancel it, so now we have to do the dance. This is a dance that so many of you have done before, and that's having. This is canceled false flag or React calls, it ignored in the official documentation, and then you have to make sure that we haven't canceled this promise. You have to have a cleanup in there and you have to do all this. So some of you just copy paste this, some of you put this in the use like a use promise or use async custom hook, which is totally fine. And you know, this works well for a while until React 18 where? Effects execute twice on mounts. So when I first discover this, I didn't know it was a React 18 thing.
I'm like, am I doing something wrong in my code? Like, what did I do? Like, you know, is, did someone change something? Was it the Tories? I have no idea, so what's actually happening over here? So I looked it up and saw, Okay. We mount an effect and then React is actually simulating an unmount and then React is remounting it again. But I was wondering, why the heck was it doing that? And so I did a lot of digging around. I was looking at the second official React docs, which is, random Twitter threads by the core maintainers. And I realized that, you know, useEffect. It was just it felt like a defective thing. I'm like, we really, really shouldn't be using it, you know? And the more we use it, you know, the more we feel like this, right? But I, you know, of course.
I calmed down a bit, researched a bit more, learned about how useEffect works. And so the answer might surprise you it doesn't. It doesn't for many effects. Okay, it actually does work, but there's many telltale signs where you could see. Okay, maybe I shouldn't be using a useEffect. First of all, React 18's double exec, that thing that it does only in strict mode and only in development environments. This is an eviction notice. Basically, when this happens in part of your app breaks, it's basically React, telling you shouldn't be using this effect inside of useEffect. Also, there's, you know, other symptoms, like lung dependency arrays when you have complex conditionals inside of your effects. If you're missing cleanup functions, or if those cleanup functions themselves are conditional. Or, you know, you might have ad hocs at state calls just littered throughout the place. These are all pretty telltale signs.
Or, you know, side effects that you know might indicate you're probably using useEffect wrong. So okay, which effects then should we be using in useEffect in the new React beta documentation, which I'll give a link to later? They say that there's really sort of two types of effects, there's the effects that, with Capital E, those effects let you specify side effects. That are caused by rendering, rather than by a particular event. So we're going to get into the difference, well, right now. So React refers to two types of effects with a capital E and events, so effects that, you know, are supposed to happen in event handlers. But I think this is a little confusing. So for the duration of this talk, I'm going to be calling them activity actions, or, sorry, activity effects in action effects, and so activity effects are something that is.
It's an ongoing process. It's something you don't forget about your, you know, while you're in the life cycle of your component. You're intently watching it, so this might be a subscription or watching a live stream of the head of Lettuce or something like that. And action effect is something where you just you, explicitly execute it. It's fire and forget. You don't care what the end results of that is like Brexit. So what is useEffect? For useEffects? The hook specifically is for synchronization with activity effects, not with action effects, but with activity effects. So one example of this is, for example, a listener, a resize, a mouse move, something like that. Where you create a handler, you add that handler and you make sure to use that cleanup function. To say, I don't want to listen for this anymore. And because activities are ongoing things, it's sort of like watching television.
Where it doesn't matter how often you turn on and off the TV, the channel is still going to keep going. You're not imposing any side effects by turning it on and off, it's not like you turn off the TV. And the BBC is like, all right, let's cut it, you know, this person, stop watching, it doesn't work that way. So where do action effects go? Then? We know that, you know, we can't really have side effects and render in. You actually sort of can. But you know, we won't get into that here. You know, we know that putting it in useEffect is awkward because, David said, so, but what about outside the component? That's an idea. So where do action effects go? In reality? They go in event handlers or like, sort of, in this example, we have an on submit and we are submitting. The data directly inside that on submit, and I'm going to show you the alternative later.
But the real mental model is you want to put your fire and forget your action effects as close as possible to the events that would have caused the effect. And so here's how this helps with React 18.. We know that, you know, when something changes, our component is going to re-render, and if we tie that effect to rendering, then React. Remember, it has the ability to just keep re-rendering that components. And because you tied it to rendering, it's going to just keep executing those effects, which we don't really want, but move the effect closer to the event handler. And you avoid that problem completely. Because you're not tying it to render, you're tying it to the actual event that caused that effect to happen, so action effects happen outside of rendering. All right, so here's an example, which actually goes through quite a few issues, and I've seen this before, I've even done this. There's an is focused prop, and so this is focus prop makes sure that, when we focus the components, so is focus equals true.
We're listening for that state change, and so when that state changes, we are going to execute that useEffect, which is then going to focus the event. So there's a lot of indirection going on here just in order to, you know, get that component to focus. And also there's a possibility of an impossible state like, what if you have multiple? Is focused is true, in short, if is, focus shouldn't really be a prop. But let's see how we could refactor this, at least outside of useEffect. So instead, we could use a forward ref, and, you know, we could just consume that in the components where we have this input graphical to useRef. And even though this is a fancy input, we could, you know, use forward wrap to grab that, or, or, if you really want to use imperative handle. I didn't put it in the slides because I want people screenshotting and being like, this guy is recommending using parative handle.
That's too spicy for this talk, but anyway, check it out. We are doing the side effect directly inside of the event handler, and so the result is we have. A very clean component, we have no useEffects in sight, and this is just extremely, extremely simple. So when we think about this, though, like executing an effect directly inside the views effect, it feels, or sorry, event handler. It feels imperative. It feels like, you know, when something happens, execute this effect. You know, feels a little weird, feels a little like side effecty and not really functional, right?
12:00 To what I see people using useEffect for where they shouldn't…
But check out the declarative approach where we're saying when something happens, causes the state to change. And depending on which parts change, this effect should be executed. But only if some condition is true and React. May execute it again for some future reason, but only in strict mode, which you shouldn't disable for some future reason. So, and I try to hear the explanation for this. I think it's something with, off-screen mode or some. Like every single time I hear it, it's just I. I lose the reason why, but just don't do it, thank you. Okay, so now we're going to get into the beta reacts docs. Just checking the time. Oh, not much time. Okay, so beta React docs. I'm so thankful that these docs exist. By the way. So, by the way, if you go to React..js..org, there's a nice header over here and you could go directly to the beta React docs.
Which we had this three years ago, but it's great that we have it now. Okay, and there's a section in here called you might not need an effect, so we're going to go over a few of the sections. Not all of them, because then this would be a much longer talk. But yeah, let's go through them one by one, especially the ones that are most common. To what I see people using useEffect for where they shouldn't, so the first one, you don't need useEffect for transforming data. So here's an example over here where we're calculating the total of a number of items right in useEffect, which makes sense, right? Whenever the items change, we need to set the total. Guess what, that's a very imperative way of thinking. Instead, what we could do is just put it inside. useMemo because total is a derived value, and used memo is very good for derived values, but. Guess what? You might not even need used memo honestly, start without it, and only if you start to know this performance problems should you start to use memos.
And use a lot. But, yeah, much simpler. Get rid of the useEffect. And of course, we know that putting set state and useEffect. It could cause a whole bunch of problems anyway. Leading people to think, I will never understand why. The default behavior of useEffect is an infinite loop. I've run into this before and the React docs warn you shouldn't do this, you know? This is why, it's because this is going to trigger a re-render, which is going to trigger the effect again, etc. Etc. Okay, you don't need useEffect for communicating with parents. This is an interesting one that really highlights the indirection that's going on. So we have on open and on closest props, and so whenever we open something here, like, let's say that this is a product View. We change the state and then, depending on whether it's open or closed, we're going to call those props. And we're doing that by putting is open in a dependency, which is going to trigger the user's effects, so you could see here.
We're clicking states changing, then we have an effect, lots of indirection, so instead, get rid of the useEffects, put it directly in the event handler. And now it becomes much more straightforward and easier to understand. So now, instead, we are just calculating the next state because we know the next state of that is open. And depending on that state, directly, call on open and on close. And then you could even put it inside of a hook. Sorry, all right, you don't need useEffect for subscribing to external stores. This one is a really interesting one because it sort of goes, counter to what I was just talking about. It's like, useEffect is really good for subscriptions, right? Well, over here. I'm subscribing to a store API and all I'm doing is getting some specific value, like whether I'm connected or not to the store API. And then I have an unsubscribe.
Here, there's still a little bit of indirection here because we are setting a local state variable. Guess what, you could actually get rid of this? And use the useSyncExternalStore hook, which has three parts a subscribe part where you tell it, here's how to subscribe, here's a function you need to call it subscribe. The get snapshot part, which is, this is the value that I want to get. And here's how to read it from that store and a server snapshot where it's just good for SSR. So you put all those three things together, it's going to automatically subscribe and get you the value you need. No use date, no useEffect. It's a really brilliant hook, so I do recommend that you at least try it out. They say it's only for library authors, but they also said, like, do not use or you will be fired is only for React maintainers. But we're seeing libraries use it for whatever reason, like Preact, signal and stuff like that, so go ahead and use it.
17:00 You don't need useEffect for fetching data…
Not not that one, but you think external store, all right, this one is a big one. You don't need useEffect for fetching data, like we talked about at the beginning, that was one of the big reasons that we used useEffect. So instead of all of this, you know that we were doing before, just use the framework, whatever you're using, you're likely using a framework. Remix has a really nice loader over here. Next, JS has get server side props and so this is going to be available to you directly in the component. And then there's a library that hopefully most of you are using for fetching data, which is React query. But notice, over here I'm doing something different, I'm doing queryClients.pre-fetch query. Because the idea is that we want to start fetching as soon as possible, which is the subject of a whole other talk. But so yeah, instead of useEffects, really, you know, useQuery, Next..js has used SWR or just use right. This is going to happen in the future.
If you're not familiar with the use hook, it basically acts the same, or it. This is going to be the new upcoming way of doing suspense. I think it's an RFC React might kill it. They like doing the whole Google thing where they, you know, kill stuff. Just kidding. They've shipped a lot of really great stuff, but the. Important thing is this cache over here, this cache means that whenever we try to read this, we are not going to keep refetching and refetching. I guarantee you're going to see React tutorials where they forget about this whole cash part, or they start fetching in. Here. You're going to get waterfalls again, so just make sure that's as early as possible and cash. And Dan talks about a lot of problems with fetching and used effect, first of all, the race conditions that we talked about. There's no instant back button. You're going to see loading Spinners everywhere when you try to go back.
Because components are going to try to load again. No initial HTML content, so you're going to have even more loading Spinners. And you're chasing waterfalls. Because the parents gonna load, and then when that's unloading, the child's gonna load, when that's done loading, etc. Etc. And so you want to avoid those things, okay? So another thing you don't need useEffect for initializing Global Singleton. So here's what I mean. Let's say that, you know, we've done this a lot, where we, execute some sort of effect right when our app starts, and so we're like, useEffect is the right place for effects, right? So, we're calling the store API dot authenticate. But we know that in React 18, this is going to run twice. So instead, we, you know, we use a ref, and we say, Hey, did this already run? You know, maybe not, but this is honestly sort of a red flag, so I recommend you not do it. So, instead of using a ref, we could just stick that outside, but guess what, why don't we just stick the entire thing outside?
Look how simple that is. A lot of you might be internally screaming.
20:00 UseEffect is for synchronization…
This is something that Dan recommended on Twitter. So I feel okay putting this on the screen. But if you're using next, you could check if type of when it was undefined or not, and call it conditionally like that. And then if you're going to scream about testing, then just wrap it in a function, you know, and then you could even inject that store API. Do whatever you want to do, you know, just do it outside of the component and then. The biggest one for me. You don't need useEffects for handling user events, and so this goes to activity effects versus action effects, so now we're talking about action effects. So in this one, we are submitting a form and we're setting all of these variables, so set is loading is true. Set the form data to the event. And so that's going to be indirect and trigger a change. This is wrong. What you should do is move it inside of the event handler, so move it inside of submit.
And then, you know, you might need to add a whole bunch of extra logic. But because you're not using useEffect and you're isolating into that event handler, you could easily abstract that to a hook. My favorite type of hook looks like this. Where you have a state and the send and all you have to do in your components is send events. This is useReducer, this is XState, it's a library, I maintained, this is Redux. Zeus. John sort of works the same way. Yeah, but so here is a bigger example. I wanted to just make this cool video where it's a thumbnail, but when you click it, it goes full screen and then there's many ways to close it. You could click outside of it, you could press the Escape key, or you could wait for the video to be over. There's three useEffects that are handling this. So the first one, we are, you know, checking is playing, and so if we are playing, the video refers, you know, we.
We have to play it, or we have to pause it. If it's not the second one. We are listening for the video to end, and if it's not ending, then we have to. Set. Is playing to false and remember, this is going to trigger some indirection, it's going to trigger the states change, and then the state is going to trigger the previous effect. And same thing over here, when we press the Escape key again, we're sending is playing to false. And now we have a little bit of cleanup too. So the way I like to think about these instead, in terms of declarative effects, is by using a state machine. And no, I'm not going to talk about Exceed in this. Talk, but you could talk to me afterwards about it. So with state machines, you declare the effects that are going to execute directly on the transition, and I think this is brilliantly simple.
So, for example, when we're in Mini and we transition to full, we're we're doing it on a toggle and we play the video. Same thing when we're going from full to Mini, we go from toggle and, we're pausing the video and then we do the transition. So, if you haven't used Sac state, just imagine this as Redux or your standard reducer, imagine if you could execute effects directly inside of your reducer? It's basically the same idea. And so I. I really like this because it really gives you a visual way. In a declarative way of thinking about all of your effects without having to just, you know, tangle your mind and think, okay, when is this effect going to execute? I have absolutely no idea. So, yeah, this is just my, you know, way of telling you, start looking into, I guess, steep machines. Because state machines are purpose built for declaratively, expressing all of these types of effects.
So, in summary or not summary, but where do action effects go? Not really event handlers, but technically they do go in state transitions, which happen to be executed at around the same time as event handlers. Now I don't have much time, so let's, wrap up with a summary. useEffect is for synchronization, activity effects go and useEffects action effects go in event handlers. You should render as you fetch, and last but not least, state transitions trigger effects. So, with that said, I want to thank you all very much. Foreign [Music].
Related talks
-
Goodbye, useEffect (ReactNext 2022)
ReactNext 2022 (Israel). 30:27, 10.6K views -
Goodbye, useEffect
Reactathon 2022. 26:00, 240.2K views -
Goodbye, useEffect
BeJS, 2022. 29:58, 516.1K views -
useWat
React Rally 2023. 32:41, 2.7K views