Missing dependencies in useEffect() really cause stale data? - javascript

I'm asking this question to confirm my understanding of some concept.
The React doc is emphatic about including all dependencies used in the useEffect() callback. As explained in the doc:
Otherwise, your code will reference stale values from previous renders.
I kind of understand where this explanation is coming from. But what concerns me is the "stale value" part. I don't see any possible way stale values can occur due to a missing dependency. My argument is also backed by what's in the doc:
Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale.
As for my understanding, if we miss listing a dependency, the effect will not run after a render caused by that dependency change because React doesn't think the effect depends on it. If I take a guess, it may be the situation when the doc refers to as referencing stale data. Indeed, that data is stale in the effect code. However, the effect callback doesn't run in the first place. I won't notice that data being stale until the effect runs. If that matters, I will first figure out why the effect didn't run and resolve the issue. I'd be confounded not by the data being stale, but by the fact that the effect wouldn't run.
What's more, let's suppose the effect runs after a render caused by another dependency change. In this scenario, even if we missed a dependency, we wouldn't read stale data thanks to the aforementioned closure reason. I experimented a bit to confirm this:
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(10);
useEffect(() => {
console.log(count2);
}, [count1]);
return (
<div className="App">
<div>
Count1: {count1}
<button onClick={() => setCount1(count1 + 1)}>increase</button>
</div>
<div>
Count2: {count2}
<button onClick={() => setCount2(count2 + 1)}>increase</button>
</div>
</div>
);
}
We always get the latest count2, as long as the effect runs. So does my understanding holds?
I want to know why React recommends the inclusion of all dependencies so much. People typically use the dependency array with the intention to bypass some effect running. If they omit a dependency, it's likely what they want. If it's a slip, they will easily notice the effect not running and take action.

I've slightly modified your example to show stale values.
Effects are often used for async reasons, so this is not unusual.
Basically it's down to closures, a first render of useEffect will create a closure on count1 & count2, if the effect is not re-run on all dependencies then these closures will stay (stale).
Clicking on count1 then means the useEffect is called again, a new instance of the setInterval is created, with a fresh (none stale) copy of count1 & count2. Because count2 is not in the dependency array, Clicking on count2 will mean the a new setInterval is not created, and stale copies of count1 & count2 are kept in memory.
To be fair, this is probably one area of Hooks that can be tricky to understand. It's easy to think of Hook Components as if there classes with data been part of the Object. But in reality hook Components are only render functions, useState / useEffect etc, are kind of side loaded into the render function pipeline. In comparison a React Class Component has it's data stored with the object instance, so this.xyz is never stale.
const {useState, useEffect} = React;
function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(10);
useEffect(() => {
const tm = setInterval(() => {
console.log(count1, count2);
}, 1000);
return () => clearInterval(tm);
}, [count1]);
return (
<div className="App">
<div>
Count1: {count1}
<button onClick={() => setCount1(count1 + 1)}>increase</button>
</div>
<div>
Count2: {count2}
<button onClick={() => setCount2(count2 + 1)}>increase</button>
</div>
</div>
);
}
ReactDOM.render(<App/>,document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="mount"></div>
<p>Increase count2, see the console not update until you increase count1,.</p>
<p>Add count2 to the dependancy, and then everything will keep in sync</p>

Your example code is showing the simplest example, in an extremely simple scenario. By default, useEffect will run on every rerender of your component. Using the dependency array, the internal function only runs when one of those values changes. I have personally run into scenarios where I forgot a dependency, my effect fired, and a value in my function had stale data. This is likely because there were several bits of process going on at once, when the one change triggered the effect, and the other piece of data hadn't caught up yet. Switching to using useReducer for controlling multiple bits of state simultaneously, instead of multiple useState, helped in some of those situations, but ultimately the dependency array kept it in line. Also (and I haven't confirmed this), the framework code for useEffect probably makes heavy use of closures so, again, it's about making sure it's referencing the data points at the right juncture in process.

Related

How can I start an async API fetch as early as possible in a React functional component?

The standard way to make an API call in functional React is with useEffect:
function Pizzeria() {
const [pizzas, setPizzas] = useState([])
useEffect(
() => fetchPizzas().then(setPizzas),
[]
)
return (
<div>
{pizzas.map((p, i) => <Pizza pizza={p} key={i} />)}
</div>
)
}
But, as this article points out, useEffect will not fire until after the component has rendered (the first time). Obviously in this trivial case it makes no difference, but in general, it would be better to kick off my async network call as soon as possible.
In a class component, I could theoretically use componentWillMount for this. In functional React, it seems like a useRef-based solution could work. (Allegedly, tanstack's useQuery hook, and probably other libraries, also do this.)
But componentWillMount is deprecated. Is there a reason why I should not do this? If not, what is the best way in functional React to achieve the effect of starting an async call early as possible (which subsequently sets state on the mounted component)? What are the pitfalls?
You're splitting milliseconds here, componentWillMount/render/useEffect all happen at essentially the same time, and the time spent fetching occurs after that. The difference in time from before to after rendering is tiny compared to the time waiting for the network when the request is sent. If you can do the fetch before the component renders, react-query's usePrefetch is nice for that.
Considering the scope of a single component, the earliest possible would be to just make the call in the component's function. The issue here is just that such statement would be executed during every render.
To avoid those new executions, you must keep some kind of "state" (or variable, if you will). You'll need that to mark that the call has been made and shouldn't be made again.
To keep such "state" you can use a useState or, yes, a useRef:
function Pizzeria() {
const pizzasFetchedRef = useRef(false)
const [pizzas, setPizzas] = useState([])
if (!pizzasFetchedRef.current) {
fetchPizzas().then(setPizzas);
pizzasFetchedRef.current = true;
}
Refs are preferred over state for this since you are not rendering the value of pizzasFetched.
The long story...
Yet, even if you use a ref (or state) as above, you'll probably want to use an effect anyway, just to avoid leaks during the unmounting of the component. Something like this:
function Pizzeria() {
const pizzasFetchStatusRef = useRef('pending'); // pending | requested | unmounted
const [pizzas, setPizzas] = useState([])
if (pizzasFetchStatusRef.current === 'pending') {
pizzasFetchStatusRef.current = 'requested';
fetchPizzas().then((data) => {
if (pizzasFetchStatusRef.current !== 'unmounted') {
setPizzas(data);
}
});
}
useEffect(() => {
return () => {
pizzasFetchStatusRef.current = 'unmounted';
};
}, []);
That's a lot of obvious boilerplate. If you do use such pattern, then creating a custom hook with it is the better way. But, yeah, this is natural in the current state of React hooks. See the new docs on fetching data for more info.
One final note: we don't see this issue you pose around much because that's nearly a micro-optimization. In reality, in scenarios where this kind of squeezing is needed, other techniques are used, such as SSR. And in SSR the initial list of pizzas will be sent as prop to the component anyway (and then an effect -- or other query library -- will be used to hydrate post-mount), so there will be no such hurry for that first call.

React hooks callback ref pattern

I often face this situation with react callbacks:
const MyComponent = ({ onLoad }) => {
useEffect => {
// some stuff
onLoad(/* args */);
}, [onLoad]);
return (<div />);
}
The problem is. I consider my component should only load one time. And with useEffect, i have to set onLoad in the dependencies, this cause any change to the onLoad prop to trigger the effect.
I generally solve this issue with a ref
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRef(_onLoad);
onLoadRef.current = _onLoad;
useEffect => {
// some stuff
onLoadRef.current(/* args */);
}, []); // No dependencies anymore
return (<div />);
}
It works well and solve a lot of similar problems, but i find it a bit ugly, and not really beginner-friendly. I wonder if there is better solutions, or if what i do is an anti-patern ?
As from the comments above: This is a good resource for how to use useEffect
https://reacttraining.com/blog/useEffect-is-not-the-new-componentDidMount/
This article specifically highlights the main reasons on why you need to think of useEffect differently from the Class Component lifecycle methods.
We often times do some setup when the component first mounts like a
network call or a subscription. We have taught ourselves to think in
terms of "moments in time" with things like componentDidMount(),
componentDidUpdate(), and componentWillUnmount(). It's natural to take
that prior knowledge of React and to seek 1:1 equivalents in hooks. I
did it myself and I think everyone does at first. Often times I'll
hear in my workshops...
"What is the hooks equivalent to [some lifecycle method]?"
The quick answer is that hooks are a paradigm shift from thinking in
terms of "lifecycles and time" to thinking in terms of "state and
synchronization with DOM". Trying to take the old paradigm and apply
it to hooks just doesn't work out very well and can hold you back.
It also gives a good run through of the useEffect and an example of converting from a Class Component to hooks.
Another good source is https://overreacted.io/a-complete-guide-to-useeffect/ from Dan Abramov. I definitely recommend this even though it's very long to read. It really helped me when I first got started using hooks to think about them the right way.
Here's a small excerpt from the beginning of the article.
But sometimes when you useEffect, the pieces don’t quite fit together.
You have a nagging feeling that you’re missing something. It seems
similar to class lifecycles… but is it really? You find yourself
asking questions like:
🤔 How do I replicate componentDidMount with useEffect?
🤔 How do I correctly fetch data inside useEffect? What is []?
🤔 Do I need to specify functions as effect dependencies or not?
🤔 Why do I sometimes get an infinite refetching loop?
🤔 Why do I sometimes get an old state or prop value inside my effect?
When I just started using Hooks, I was confused by all of those
questions too. Even when writing the initial docs, I didn’t have a
firm grasp on some of the subtleties. I’ve since had a few “aha”
moments that I want to share with you. This deep dive will make the
answers to these questions look obvious to you.
To see the answers, we need to take a step back. The goal of this
article isn’t to give you a list of bullet point recipes. It’s to help
you truly “grok” useEffect. There won’t be much to learn. In fact,
we’ll spend most of our time unlearning.
It’s only after I stopped looking at the useEffect Hook through the
prism of the familiar class lifecycle methods that everything came
together for me.
In terms of the original question above, using refs is a good way to be able to not have your effect have specific functions and values as dependencies.
In particular they are good if you "you want to read the latest rather than captured value inside some callback defined in an effect"
For this example from the poster:
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRef(_onLoad);
onLoadRef.current = _onLoad;
useEffect => {
// some stuff
onLoadRef.current(/* args */);
}, []); // No dependencies anymore
return (<div />);
}
This is a completely valid way of doing things, though depending on the args that onLoad takes, and how it works, it might be a good idea to add extra items to the dependency array to make it always in sync.
You could abstract away the wonkiness of the useRef here, but unfortunately the rules of hooks eslint plugin wouldn't recognize it as a ref. It would work, you'd just need to add the onLoadRef to the dependency array, though it would never cause the effect to re-run. It's similar to things like dispatch from react-redux where you know it is stable, but the eslint plugin can't know that.
function useRefUpdater(value) {
const ref = useRef(value);
// I forget where I saw that you should change the ref in a useEffect
useEffect(() => {
ref.current = value;
}, [value]);
return ref;
}
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRefUpdater(_onLoad)
useEffect(() => {
// some stuff
onLoadRef.current(/* args */);
}, []);
// React Hook useEffect has a missing dependency: 'onLoadRef'. Either include it or remove the dependency array.
return <div />;
};

React - error in useEffect(). does dat make everything on page go in infinite loop?

I've converted all my class components to functional components. Now, when I change some code in the IDE during development my page goes in an infinite loop. I'm almost certain one on my useEffect() hooks has an mistake (or missing) conditional firing variable. But it would take some time to figure out which one.
So, my question is. Is there an easy way to figure out which one is broken that would cause the loop. And would it also loop after build, or only in development?
As requested a code example. It is very basic since I have about 20 using this principle.
import React, {useEffect} from "react";
const Layout = ({ data }) => {
useEffect(() => {
// some jQuery stuff
}, [data.myConditionalVar])
return (
<div>
// my stuff
</div>
)
}
useEffect gets called every time the second argument you pass is changed. In your case it is data.myConditionalVar.
So, I am assuming inside useEffect you are updating the data using jquery which forces React to call the useEffect again resulting in a never-ending loop.

Change handlers in react hooks

For a while a would like to start using react function components with react hooks instead of class extending react component, but there is one thing which discourages me. Here is an example from very first intro of react hooks:
import React, { useState } from 'react'
import Row from './Row'
export default function Greeting(props) {
const [name, setName] = useState('Mary');
function handleNameChange(e) {
setName(e.target.value);
}
return (
<section>
<Row label="Name">
<input
value={name}
onChange={handleNameChange}
/>
</Row>
</section>
)
}
There is a handleNameChange declaration used as a change handler for input. Let's imagine that Greeting component updates really really frequently because of some reason. Does change handle initialize every time on every render? From JavaScript aspect of view how bad is that?
Does change handle initialize every time on every render?
Yes. That's one reason for the useCallback hook.
From JavaScript aspect of view how bad is that?
Fundamentally, it's just creating a new object. The function object and the underlying function code are not the same thing. The underlying code of the function is only parsed once, usually into bytecode or a simple, quick version of compilation. If the function is used often enough, it'll get aggressively compiled.
So creating a new function object each time creates some memory churn, but in modern JavaScript programming we create and release objects all the time so JavaScript engines are highly optimized to handle it when we do.
But using useCallback avoids unnecessarily recreating it (well, sort of, keep reading), by only updating the one we use when its dependencies change. The dependencies you need to list (in the array that's the second argument to useCallback) are things that handleNameChange closes over that can change. In this case, handleNameChange doesn't close over anything that changes. The only thing it closes over is setName, which React guarantees won't change (see the "Note" on useState). It does use the value from the input, but it receives the input via the arguments, it doesn't close over it. So for handleNameChange you can leave the dependencies blank by passing an empty array as the second argument to useCallback. (At some stage, there may be something that detects those dependencies automatically; for now, you declare them.)
The keen-eyed will note that even with useCallback, you're still creating a new function every time (the one you pass in as the first argument to useCallback). But useCallback will return the previous version of it instead if the previous version's dependencies match the new version's dependencies (which they always will in the handleNameChange case, because there aren't any). That means that the function you pass in as the first argument is immediately available for garbage collection. JavaScript engines are particularly efficient at garbage collecting objects (including functions) that are created during a function call (the call to Greeting) but aren't referenced anywhere when that call returns, which is part of why useCallback makes sense. (Contrary to popular belief, objects can and are created on the stack when possible by modern engines.) Also, reusing the same function in the props on the input may allow React to more efficiently render the tree (by minimizing differences).
The useCallback version of that code is:
import React, { useState, useCallback } from 'react' // ***
import Row from './Row'
export default function Greeting(props) {
const [name, setName] = useState('Mary');
const handleNameChange = useCallback(e => { // ***
setName(e.target.value) // ***
}, []) // *** empty dependencies array
return (
<section>
<Row label="Name">
<input
value={name}
onChange={handleNameChange}
/>
</Row>
</section>
)
}
Here's a similar example, but it also includes a second callback (incrementTicks) that does use something it closes over (ticks). Note when handleNameChange and incrementTicks actually change (which is flagged up by the code):
const { useState, useCallback } = React;
let lastNameChange = null;
let lastIncrementTicks = null;
function Greeting(props) {
const [name, setName] = useState(props.name || "");
const [ticks, setTicks] = useState(props.ticks || 0);
const handleNameChange = useCallback(e => {
setName(e.target.value)
}, []); // <=== No dependencies
if (lastNameChange !== handleNameChange) {
console.log(`handleNameChange ${lastNameChange === null ? "" : "re"}created`);
lastNameChange = handleNameChange;
}
const incrementTicks = useCallback(e => {
setTicks(ticks + 1);
}, [ticks]); // <=== Note the dependency on `ticks`
if (lastIncrementTicks !== incrementTicks) {
console.log(`incrementTicks ${lastIncrementTicks === null ? "" : "re"}created`);
lastIncrementTicks = incrementTicks;
}
return (
<div>
<div>
<label>
Name: <input value={name} onChange={handleNameChange} />
</label>
</div>
<div>
<label>
Ticks: {ticks} <button onClick={incrementTicks}>+</button>
</label>
</div>
</div>
)
}
ReactDOM.render(
<Greeting name="Mary Somerville" ticks={1} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
When you run that, you see that handleNameChange and incrementTicks were both created. Now, change the name. Notice that nothing is recreated (well, okay, the new ones aren't used and are immediately GC'able). Now click the [+] button next to ticks. Notice that incrementTicks is recreated (because the ticks it closes over was stale, so useCallback returned the new function we created), but handleNameChange is still the same.
Looking strictly from a JavaScript perspective (ignoring React), defining a function inside a loop (or inside another function that's called regularly) is unlikely to become a performance bottleneck.
Take a look at these jsperf cases. When I run this test, the function declaration case runs at 797,792,833 ops/second. It's not necessarily a best practice either, but it's often a pattern that falls victim to premature optimization from programmers who assume that defining a function must be slow.
Now, from React's perspective. It can become a challenge for performance is when you are passing that function to child components who end up re-rendering because it's technically a new function each time. In that case it becomes sensible to reach for useCallback to preserve the identity of the function across multiple renders.
It's also worth mentioning that even with the useCallback hook, the function expression is still redeclared with each render, it's just that it's value is ignored unless the dependency array changes.

What is the difference between using setItem to set multiple state values and using useEffect to do so?

I have a reset button in my app that resets a few variables of my functional component:
const [selectedItem, setSelectedItem] = useState(0);
const [a, setA] = useState('a');
const [b, setB] = useState('blue');
<button onClick={e => ???}>clicky</button>
<button onClick={e => ???}>clicky</button>
There are two ways I could 'reset' the data: monitoring selectedItem for changes using useEffect, or have a handler that does so:
<button onClick ={e => setSelectedItem(e.target.value)} />
useEffect(() => {
setA(Math.random())
setB(Math.random())
}, [selectedItem])
or
<button onClick ={e => handler(e.target.value)} />
const handler = item => {
setSelectedItem(Math.random())
setA(Math.random())
setB(Math.random())
}
What are the practical differences between these approaches? The hooks docs say to use useEffect for performing side effects, but I can't see why this approach wouldn't work as well.
What is the difference between these approaches?
I will try to answer this question in three points.
Mental model
You need to "think in effects". the UseEffect hook lets you perform side-effects that manly need to happen async like (fetch Data from API, manipulate the DOM).
based on that it's better to use UseEffect to handle side-effects so you are not confusing your colleges.
Async
You need to keep in your mind that useEffect is an async function but your event handler is sync function. That can lead to totally different behavior maybe you are not seeing a weird behavior here but maybe in other examples, you will start to notice that.
React mechanism
the last difference to notice it you need to understand React update state mechanism, react makes patches to update the state. That means in your event handler the three-state will cause one re-render because they will happen at the same time. In your useEffect that is not the case, you are updating one of them that case re-render then you are performing the effect that will case new re-render.
Maybe there are other differences but that what can I see right now.
I hope it’s a useful answer.
There are a few peculiar differences between the above two methods.
In the first method of using useEffect, you would be updating states a and b whenever selectedItem changes, be it by a button click or some other sideeffect such as a prop change. However in the second case, states a and b would only be updated if selectedItem is updated on button click and you would need to call setA and setB to update states everywhere you update selectedItem separately
Secondly, when you are using a useEffect to update state, the state update will happen after updating selectedItem, however in the second case state updates doesn't gurantee that selectedItem is updated before setting the other states and hence if the other state updated depend on selectedItem value, you need to pass the updated selectedItem value to the other state updaters separately
In short, making use of useEffect is better when you know you have to take other actions whenever a state change occurs no matter how it occurs. Also its useful when you want to take action after a particular state is updated.
I believe it's important to consider the semantics of what you're doing. For example:
<button onClick ={e => handler(e.target.value)} />
const handler = item => {
setSelectedItem(Math.random())
setA(Math.random())
setB(Math.random())
}
this means that whenever you click the button you want the 3 state variables to be changed.
On the other hand:
<button onClick ={e => setSelectedItem(e.target.value)} />
useEffect(() => {
setA(Math.random())
setB(Math.random())
}, [selectedItem])
this means whenever you click the button you want that one state variable to change and independently of that you want, whenever that one state variable changes, to change those other two state variables.
The real question you should be asking is what is it you really want to express with your code, given that it has the same end result. In short, what makes semantic sense to you? Does it make sense to say "this button can be used to change those 3 state variables" or does it make more sense to say "this button can be used to chanage the selectedItem state variable and this entire component will change the a and b state variables whenever the selectedItem changes?
It is usually important to make sure your code makes semantic sense so you don't land in the pitfalls of getting unintended side-effects when you make code changes. For example, if selectedItem ends up being changeable by other means, the 2nd method will ensure that a and b change at the same time. Do you really want that?
There's also a practical consideration. There's the eslint rule called react/no-did-update-set-state which states:
Updating the state after a component update will trigger a second render() call and can lead to property/layout thrashing.
Layout thrashing basically means there's multiple potential redraws of the layout before a user can interact with it again. In the case of useEffect this can be an issue because useEffect is triggered after a layout update and setting the state might trigger another one. It usually has no noticeable effect on very simple operations but if you have a complex component hierarchy and end up re-rendering large portions of it then you will end up with a less responsive layout.
There's also the additional consideration that with the useEffect you also need to be mindful to avoid cyclical dependency changes e.g. selectedItem changes a and a changes selectedItem or makes a change which ends up changing selectedItem somewhere further down the line.
So overall there are three notes:
Use whichever makes more semantic sense for your component
Be aware of potential layout thrashing
If useEffect does make more sense take a step back and really think about why it makes more sense and whether there is a better way to solve your problem and at the same time avoid using useEffect to set state variables.
Ok the scenario you are referring to here is not really a side-effect, or I would say the side-effect react refers to. React refers to side-effect like if you are doing a network request
In your case if you just want to reset some variables I think having a clickHandler is the way to do, you would use a useEffect like if you want to do a network request when the component loads or some props change
Hope it clarifies

Categories

Resources