Edit
Okay my bad, I guess the response is simple. It's a javascript matter. I am not passing a function to useState but actually I am re-executing it everytime. So yeah what I am seeing is normal.
I will let my question here anyway.
A codesandbox example will follow.
Question :
Could someone please explain to me why React re-execute the inner function in a useState (or useRef) despite the returned value will be completely ignored (except the first execution) ? Is this the expected behaviour of useState ?
Explanation :
I mean why I see this console log here after each re-render
const [count, setCount] = React.useState((function(){
console.log('Executed !');
return 5;
})())
I used to think this inner function (or values) is the equivalent of a Class Parameters or Component Constructor but it's obviously not the case.
Returning a simple value does the job of course : React.useState(5).
But imagine using a third-party class which works with static values at some point or you simply don't want to re-execute it over and over again after each re-render ?
A possible fix for this is using useEffect with an empty dependency. (The equivalent of componentDidMount)
But I am asking about the purpose of this execution ?
let count = 0; // Number of component renders
const Test = (props) => {
const [innerCount, setCount] = React.useState((function(){})(
console.log('Executed !'); // Executed after each re-render
count = count + 1;
return count;
));
return innerCount; // Will be always 1 !
}
Why the anonymous function will be executed if the returned value is always 1 = the first returned value ?! What's the purpose of this execution ?
This is an example on CodeSandbox
This isn't React-specific behaviour, it's just what Javascript - and almost every mainstream programming language - does.
Before calling a function, it has to evaluate its arguments in order to know what to pass in.
Yes, it so happens that React.useState is a function that often "ignores" its argument. (I'm not familiar with precisely how it's implemented - clearly it isn't as simple as that as it does need to know when it's the first time it's being executed in that particular component instance, because in that case that value is used and returned.) But the browser Javascript engine can't possibly know that or optimise it away.
To prove this isn't anything to do with React, I can do this:
function ignoresArgument(x) {
return 1;
}
for (let i = 0; i < 20; i++) {
ignoresArgument(
(function(y) {
console.log("I don't need to be executed, but Javascript doesn't know that so I will be executed 20 times");
return 2;
})(1)
);
}
In addition to all the other answers, I think this link addresses your issue where you only want to call the function in useState once.
You can change this line in the sandbox:
const [dependency] = React.useState(() => new ThirdParty());
and the third party counter would stop incrementing.
I think your understanding of useEffect is somewhat wrong. It is not just used for componentDidMount. What you are describing is a symptom of a side-effect that cases state changes in the components. Here is the explanation of useEffect from the official documentation.
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
useEffect would be ideal for this situation if you want to avoid re-renders that is not required.
React memo might not be needed for your solution either.
Related
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.
Say I have a simple component like this one:
export default function Foo({someProp}) {
const a = Math.random();
return <div>{a}{someProp}</div>
}
As far as I know, when someProp updates, React will trigger a re-render. Will it execute the whole Foo function once again and reassign const a a new random value? Will the value be displayed in the <div>?
Thank you.
The answer for every one of your question is yes. A re-render is triggered when there is a props change as you said, and also when there is a state change. When re-rendering and also on the first render, everything behaves like in a normal JavaScript function, as far as assigning variables and everything else, except for some things related to React Hooks, like a state made with useState, a ref made with useRef...
if i used setInterval(line 15) without useEffect than it gives result 2^n-1(0,1,3,7,15,31,63...) instead of(0,1,2,3,4,..) . so i have some question
1)why i am getting that output when I directly called setInterval without useeffect 2)is there any way if I change setCount(line 9) and its gives correct output by use setInterval directly without useEffect(as I did)
3) if the use of setInterval is not possible without useEffcet than why it is not possible?
if i put setInterval in useEffect and render initially once( line 12,13,14) than it gives correct output.....
but I do not get the correct output when I use directly setInterval. what is diff bet them?
in both cases, I call setInterval once but the output is diff.
import React, {useEffect, useState } from 'react'
export default function IncorrectDependency() {
const [count,setCount]=useState(0)
const inc=()=>{
// console.log(count)
setCount(preVal=>preVal+1)
// setCount(count+1)
}
// useEffect(()=>{
// setInterval(inc,1000)},[]
// )
setInterval(inc,1000)
return (
<div>
<h1>{count}</h1>
</div>
)
}
When we do a set state, functional components will re-execute from top to bottom, how ever when we use useState, useCallbacks etc.. they will not re-initialize as variables, functions,
So in this case, setInterval will re-initialize on each and every setCount, because of the state got changed,
step by step
in the 1st second there will be one setInterval, and call setCount and component is ready to rerender
when re-redering, start executing functional component from top-to-bottom it sees setInterval again and it will trigger it, so now we have two setIntervals
so on it will add multiple setIntervals on each second, because we don't clear it, so you should see the number printed in the browser will not take a second, but less than a second when time goes by.
You can achieve the expected result without useEffect by clearing the previous interval on each re-render which is happen due to setCount
create a variable to hold the set interval, code
const interval = null;
//this should be declare out side the component,
//because if we declare it inside the component it will redeclare,
//and the reference to the previous setInterval will be lost in that case no-way to clear the setInterval.
export default function IncorrectDependency() {
....
if (interval) {
clearInterval(interval);
}
interval = setInterval(inc, 1000);
....
}
alternatively react has a hook which can hold the same variables without re-initializing on each renderings, check it out useRef
here is a code-demo
const intvl = useRef(null);
....
if (intvl?.current) {
clearInterval(intvl.current);
}
intvl.current = setInterval(inc, 1000);
.....
when you directly use setInterval what is happening as this is a function it will be called on state change so again a setInterval will be triggered and so on which actually give you the incorrect result, so you shouldn't use setInterval without use effect, also on unmount you should clearthe interval
Dan Abramov explains why this isn't a good idea:
"it’s not the idiomatic way to do it. eg it won’t work correctly if you have multiple instances of the same component. it breaks the rules — it does a side effect (setInterval) during rendering, which the page said you should not do :) once you break the rules, all bets are off"
https://twitter.com/dan_abramov/status/1577395235340095501?s=20&t=nNnYyjLvHs5by_dqF5l8zg
This question already has answers here:
Why can't I directly modify a component's state, really?
(7 answers)
Closed 2 years ago.
Using hooks to update state based on the previous state value, I don't understand why modifying the existing object and passing that to setState() is bad. I know this won't cause a re-render since the state still points to the same reference, but beyond that, what is the issue? I don't understand how cloning the array, modifying that, then passing it to setState() fixes some unknown issue.
const [bigArr, setBigArr] = setState(Array(SOME_BIG_NUMBER).fill(false));
// (1) This seems to work, but is bad for some reason. But why?
bigArr[325] = true;
setBigArr(bigArr);
// (2) This is preferable for some reason. Why?
bigArrCopy = bigArr.slice();
bigArrCopy[325] = true;
setBigArr(bigArrCopy);
// (3) Is this OK? Why/Why not?
setBigArr(bigArrCopy => {
bigArrCopy[325] = true;
return bigArrCopy;
});
I know this won't cause a re-render since the state still points to the same reference, but beyond that, what is the issue?
Is that not enough? The reason to set state is because you want the component to rerender. If you're trying to get it to rerender and it doesn't, that's a pretty serious bug.
The underlying reason why react went with a model of immutable state is that it makes it very simple to tell whether state changed. Do a quick === between two states, and you immediately know whether it has changed. If you mutate your state, this feature is lost, and any code that depends on it breaks.
The first case will not work as expected, it will not re-render the component because React use shallow comparison which means that it will compare location of object and array, if the location not change React will not trigger re-render
// (1) This will not re-render the component, cuz the location of bigArr are not changed
bigArr[325] = true;
setBigArr(bigArr);
Happen the same with third case
You may say you can fix it by call setBigArr([...bigArr]);. But there is still problems
This is an simple situation you get unexpected result
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [arr, setArr] = useState([1]);
const handleClick = () => {
createTimeout()
// Case1: directly set state
arr[0]= 2
setArr(arr);
// Case1: Not directly set state
// const newArr = [...arr]
// newArr[0]= 2
// setArr(newArr);
};
const createTimeout = () =>{
setTimeout(() =>{
// You expect number 1 here because you call this before update state
console.log(arr[0])
},2000)
}
return (
<div className="App">
<h1>{arr[0]}</h1>
<div onClick={handleClick}>change</div>
</div>
);
}
We call createTimeout before setState so we will expect number 1 will be logged but:
Case 1: you will get number 2 because you mutated original array
Case 2: you will get number 1 (expected)
Check out the codesandbox
React checks to see if the bigArrState !== prevBigArrState before re-rendering. It does not check the contents of the array. It checks if it's the same instance. In your first example that will result in false, and the component will not re-render. When you use bigArr.slice(), you are creating an entire new array therefore bigArrState !== prevBigArrState results in true, allowing the component to re-render.
Your last example will causes issues because the updater func does not get passed bigArrCopy but rather bigArrState (same instance).
https://reactjs.org/docs/react-component.html#setstate
Rather than creating and storing an entire clone in memory you can do the following:
setBigArr([
...bigArr.slice(0, 325),
true,
...bigArr.slice(326),
]);
Here is an example of a mutable ref storing the current callback from the Overreacted blog:
function useInterval(callback, delay) {
const savedCallback = useRef();
// update ref before 2nd effect
useEffect(() => {
savedCallback.current = callback; // save the callback in a mutable ref
});
useEffect(() => {
function tick() {
// can always access the most recent callback value without callback dep
savedCallback.current();
}
let id = setInterval(tick, delay);
return () => clearInterval(id);
}, [delay]);
}
However the React Hook FAQ states that the pattern is not recommended:
Also note that this pattern might cause problems in the concurrent mode. [...]
In either case, we don’t recommend this pattern and only show it here for completeness.
I found this pattern to be very useful in particular for callbacks and don't understand why it gets a red flag in the FAQ. For example, a client component can use useInterval without needing to wrap useCallback around the callback (simpler API).
Also there shouldn't be a problem in concurrent mode, as we update the ref inside useEffect. From my point of view, the FAQ entry might have a wrong point here (or I have misunderstood it).
So, in summary:
Does anything fundamentally speak against storing callbacks inside mutable refs?
Is it safe in concurrent mode when done like it is in the above code, and if not, why not?
Minor disclaimer: I'm not a core react dev and I haven't looked at the react code, so this answer is based on reading the docs (between the lines), experience, and experiment
Also this question has been asked since which explicitly notes the unexpected behaviour of the useInterval() implementation
Does anything fundamentally speak against storing callbacks inside mutable refs?
My reading of the react docs is that this is not recommended but may still be a useful or even necessary solution in some cases hence the "escape hatch" reference, so I think the answer is "no" to this. I think it is not recommended because:
you are taking explicit ownership of managing the lifetime of the closure you are saving. You are on your own when it comes to fixing it when it gets out of date.
this is easy to get wrong in subtle ways, see below.
this pattern is given in the docs as an example of how to work around repeatedly rendering a child component when the handler changes, and as the docs say:
it is preferable to avoid passing callbacks deep down
by e.g. using a context. This way your children are less likely to need re-rendering every time your parent is re-rendered. So in this use-case there is a better way to do it, but that will rely on being able to change the child component.
However, I do think doing this can solve certain problems that are difficult to solve otherwise, and the benefits from having a library function like useInterval() that is tested and field-hardened in your codebase that other devs can use instead of trying to roll their own using setInterval directly (potentially using global variables... which would be even worse) will outweigh the negatives of having used useRef() to implement it. And if there is a bug, or one is introduced by an update to react, there is just one place to fix it.
Also it might be that your callback is safe to call when out of date anyway, because it may just have captured unchanging variables. For example, the setState function returned by useState() is guaranteed not to change, see the last note in this, so as long as your callback is only using variables like that, you are sitting pretty.
Having said that, the implementation of setInterval() that you give does have a flaw, see below, and for my suggested alternative.
Is it safe in concurrent mode, when done like in above code (if not, why)?
Now I don't exactly know how concurrent mode works (and it's not finalized yet AFAIK), but my guess would be that the window condition below may well be exacerbated by concurrent mode, because as I understand it it may separate state updates from renders, increasing the window condition that a callback that is only updated when a useEffect() fires (i.e. on render) will be called when it is out of date.
Example showing that your useInterval may pop when out of date.
In the below example I demonstrate that the setInterval() timer may pop between setState() and the invocation of the useEffect() which sets the updated callback, meaning that the callback is invoked when it is out of date, which, as per above, may be OK, but it may lead to bugs.
In the example I've modified your setInterval() so that it terminates after some occurrences, and I've used another ref to hold the "real" value of num. I use two setInterval()s:
one simply logs the value of num as stored in the ref and in the render function local variable.
the other periodically updates num, at the same time updating the value in numRef and calling setNum() to cause a re-render and update the local variable.
Now, if it were guaranteed that on calling setNum() the useEffect()s for the next render would be immediately called, we would expect the new callback to be installed instantly and so it wouldn't be possible to call the out of date closure. However the output in my browser is something like:
[Log] interval pop 0 0 (main.chunk.js, line 62)
[Log] interval pop 0 1 (main.chunk.js, line 62, x2)
[Log] interval pop 1 1 (main.chunk.js, line 62, x3)
[Log] interval pop 2 2 (main.chunk.js, line 62, x2)
[Log] interval pop 3 3 (main.chunk.js, line 62, x2)
[Log] interval pop 3 4 (main.chunk.js, line 62)
[Log] interval pop 4 4 (main.chunk.js, line 62, x2)
And each time the numbers are different illustrates the callback has been called after the setNum() has been called, but before the new callback has been configured by the first useEffect().
With more trace added the order for the discrepancy logs was revealed to be:
setNum() is called,
render() occurs
"interval pop" log
useEffect() updating ref is called.
I.e. the timer pops unexpectedly between the render() and the useEffect() which updates the timer callback function.
Obviously this is a contrived example, and in real life your component might be much simpler and not actually be able to hit this window, but it's at least good to be aware of it!
import { useEffect, useRef, useState } from 'react';
function useInterval(callback, delay, maxOccurrences) {
const occurrencesRef = useRef(0);
const savedCallback = useRef();
// update ref before 2nd effect
useEffect(() => {
savedCallback.current = callback; // save the callback in a mutable ref
});
useEffect(() => {
function tick() {
// can always access the most recent callback value without callback dep
savedCallback.current();
occurrencesRef.current += 1;
if (occurrencesRef.current >= maxOccurrences) {
console.log(`max occurrences (delay ${delay})`);
clearInterval(id);
}
}
let id = setInterval(tick, delay);
return () => clearInterval(id);
}, [delay]);
}
function App() {
const [num, setNum] = useState(0);
const refNum = useRef(num);
useInterval(() => console.log(`interval pop ${num} ${refNum.current}`), 0, 60);
useInterval(() => setNum((n) => {
refNum.current = n + 1;
return refNum.current;
}), 10, 20);
return (
<div className="App">
<header className="App-header">
<h1>Num: </h1>
</header>
</div>
);
}
export default App;
Alternative useInterval() that does not have the same problem.
The key thing with react is always to know when your handlers / closures are being called. If you use setInterval() naively with arbitrary functions then you are probably going to have trouble. However, if you ensure your handlers are only called when the useEffect() handlers are called, you will know that they are being called after all state updates have been made and you are in a consistent state. So this implementation does not suffer in the same way as the above one, because it ensures the unsafe handler is called in useEffect(), and only calls a safe handler from setInterval():
import { useEffect, useRef, useState } from 'react';
function useTicker(delay, maxOccurrences) {
const [ticker, setTicker] = useState(0);
useEffect(() => {
const timer = setInterval(() => setTicker((t) => {
if (t + 1 >= maxOccurrences) {
clearInterval(timer);
}
return t + 1;
}), delay);
return () => clearInterval(timer);
}, [delay]);
return ticker;
}
function useInterval(cbk, delay, maxOccurrences) {
const ticker = useTicker(delay, maxOccurrences);
const cbkRef = useRef();
// always want the up to date callback from the caller
useEffect(() => {
cbkRef.current = cbk;
}, [cbk]);
// call the callback whenever the timer pops / the ticker increases.
// This deliberately does not pass `cbk` in the dependencies as
// otherwise the handler would be called on each render as well as
// on the timer pop
useEffect(() => cbkRef.current(), [ticker]);
}