Why is ref value incrementing twice - javascript

I have this code here:
export default function App() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(0);
const reff = useRef(0);
useEffect(() => {
const id = setInterval(() => {
setCount((c) => {
console.log({ step }, "reff.current before incr ", reff.current);
reff.current = reff.current + step;
console.log("ref.curret after incr ", reff.current);
return c + step;
});
}, 6000);
return () => clearInterval(id);
}, [step]);
return (
<>
<h1>Count:{count}</h1>
<h1>Step: {step}</h1>
<input
type="button"
onClick={(e) => setStep((prevStep) =>(prevStep + 1))}
value="+ step"
/>
</>
);
}
You can view it here: https://codesandbox.io/s/gracious-roentgen-31ntb?file=/src/App.js:0-817
Note that I'm incrementing ref inside setCount callback
When the component loads a setInterval is started. If I trigger the useEffect again by incrementing step before 6 seconds pass, it clears the setInterval and creates a new one.
And now if I don't increment step again and wait for 6 seconds, I see step: 1 reff.current before incr, 0 for the first time the setInterval callback is called.
After incrementing in the next line(reff.current = reff.current + step) I see "ref.curret after incr ", 1
When the setInterval callback is called again after 6 seconds, I see
step: 1 reff.current before incr, 2 //how did this become 2
I don't understand how the value of reff.current is 2.
This only happens when I increment the step(which clears the first interval). If I set the initial step to 1 and don't increment it, I see expected values.. Just checked again. It doesn't work as expected.
I can't understand why the value of reff.current is 2 when the setInterval callback is called the second time.
Go to the sandbox link and
Click on the +step button once
Open the console and see
log 1:{step: 1} "reff.current before incr "0
log 2: ref.curret after incr 1
//second time callback is called
log 3: {step: 1} "reff.current before incr "2 //this should be 1
log 4: ref.curret after incr 3```

Your problem happens because you are performing a side effect (updating reff.current) inside the updater function passed to the setCount.
The beta react docs say that:
Updater functions run during rendering, so updater functions must be
pure and only return the result. Don’t try to set state from inside of
them or run other side effects. In Strict Mode, React will run each
updater function twice (but discard the second result) to help you
find mistakes.
You are running your app in StrictMode. To verify it comment it:
const rootElement = document.getElementById('root');
ReactDOM.render(
// <StrictMode>
<App />,
// </StrictMode>,
rootElement
);
and the problem doesn't happen anymore.

Can you please try out this code below,
Not sure about the exact reason why the value was incremented, but using it directly instead of returning from a function, did work as expected. Also it works for further step increment.
import { useEffect, useRef, useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(0);
const reff = useRef(0);
useEffect(() => {
const id = setInterval(() => {
console.log({ step }, "reff.current before incr ", reff.current);
debugger;
// ------ modified here
let newVal = reff.current + step;
reff.current = newVal;
console.log("ref.curret after incr ", reff.current);
setCount(newVal);
// ------ modified here
}, 6000);
return () => clearInterval(id);
}, [step]);
return (
<>
<h1>Count:{count}</h1>
<h1>Step: {step}</h1>
<input
type="button"
onClick={(e) => {
e.preventDefault();
setStep((prevStep) => {
console.log(prevStep);
return Number(prevStep) + 1;
});
}}
value="+ step"
/>
</>
);
}
The image of the log

Related

Should inline functions never be passed to child components? [duplicate]

let's say we have the components like this
const Example = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
When I passed the onClick handler as an arrow function, my eslint throw a warning:
error JSX props should not use arrow functions react/jsx-no-bind
As I read from an answer from this post: https://stackoverflow.com/questions/36677733/why-shouldnt-jsx-props-use-arrow-functions-or-bind#:~:text=Why%20you%20shouldn't%20use,previous%20function%20is%20garbage%20collected.
The short answer is because arrow function is recreated every time, which will hurt the performance.
One solution proposed from this post is to wrapped in a useCallback hook, with empty array. And when I change to this, the eslint warning really disappear.
const Example = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
However, there is also another opinion saying that overusing useCallback will eventually slowdown the performance due to the overheads of useCallback. One example is here: https://kentcdodds.com/blog/usememo-and-usecallback
This is making me really confused? So for Functional Components, when dealing with inline function handler, should I just write the arrow function (ignore the eslint) or always wrap it in a useCallback ???
The short answer is because arrow function is recreated every time, which will hurt the performance.
This is a common misconception. The arrow function is recreated every time either way (although with useCallback subsequent ones may be thrown away immediately). What useCallback does is make it possible for the child component you use the callback on to not re-render if it's memoized.
Let's look at the misconception first. Consider the useCallback call:
const increment = useCallback(() => setCounter(counter => counter + 1), []);
That's executed like this:
Evaluate the first argument, () => setCounter(counter => counter + 1), creating a function
Evaluate the second argument, [], creating an array
Call useCallback with those two arguments, get back a function
Compare with what you have if you don't use useCallback:
const increment = () => setCounter(counter => counter + 1);
That's much simpler: Create the function. It doesn't then have to do #2 and #3 above.
Let's move on to what useCallback actually does that's useful. Let's look at where the callback is used:
<Button onClick={increment} />
Now, suppose Button is memoized with React.memo or similar. If increment changes every time your component renders, then Button has to re-render every time your component changes; it can't be reused between renders. But if increment is stable between renders (because you used useCallback with an empty array), the memoized result of calling Button can be reused, it doesn't have to be called again.
Here's an example:
const { useState, useCallback } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const increment = useCallback(
() => setCount(count => count + 1),
[]
);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Note that clicking the button in ComponentA always calls Button again, but clicking the button in ComponentB doesn't.
When do you want to do that? That's largely up to you, but it probably makes sense when your component's state will change frequently in ways that don't affect the contents of increment and thus don't affect Button and if Button has to do significant work when rendered. Button probably doesn't, but other child components may.
For instance, the useCallback in my previous example is probably pointless if you use count as the text of the button, since that means Button has to re-render regardless:
const { useState, useCallback } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
<Button onClick={increment}>{count}</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const increment = useCallback(
() => setCount(count => count + 1),
[]
);
return (
<div>
<Button onClick={increment}>{count}</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Also note that useCallback isn't free, it impacts the code in the callback. Look at the code in the callbacks in ComponentA and ComponentB in the examples. ComponentA (which doesn't use useCallback) can use the value of count that it closes over (within limits!), () => setCount(count + 1). But the one in ComponentB always has to use the callback form of the setter, () => setCount(count => count + 1). That's because if you keep using the first increment you create, the count it closes over will be stale — you'd see the count go to 1, but never further.
A final note: If you're re-rendering a component so often that creating and throwing away the various functions you're passing to useCallback or useMemo may be causing too much memory churn (a rare situation), you can avoid it by using a ref. Let's look at updating ComponentB to using a ref instead of useCallback:
const incrementRef = useRef(null);
if (!incrementRef.current /* || yourDependenciesForItChange*/) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
incrementRef.current = () => setCount(count => count + 1);
}
const increment = incrementRef.current;
That only creates the increment function once (in that example, since we don't have any dependencies), it doesn't create and throw away functions like using useCallback does. It works because the initial value of the ref is null, and then the first time the component function is called, we see that it's null, create the function, and put it on the ref. So increment is only created once.
That example does recreate the function we pass setCount every time increment is called. It's possible to avoid that, too:
const incrementRef = useRef(null);
if (!incrementRef.current) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const incrementCallback = count => count + 1;
incrementRef.current = () => setCount(incrementCallback);
}
const increment = incrementRef.current;
const { useState, useRef } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
const incrementRef = useRef(null);
if (!incrementRef.current) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const incrementCallback = count => count + 1;
incrementRef.current = () => setCount(incrementCallback);
}
const increment = incrementRef.current;
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
That's really going to 11 in terms of avoiding unnecessary function creation. :-)
It's a rare component that needs even that first level of optimization, much less the second level; but when/if you do, that's how you do it.

Should we use useCallback in every function handler in React Functional Components

let's say we have the components like this
const Example = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
When I passed the onClick handler as an arrow function, my eslint throw a warning:
error JSX props should not use arrow functions react/jsx-no-bind
As I read from an answer from this post: https://stackoverflow.com/questions/36677733/why-shouldnt-jsx-props-use-arrow-functions-or-bind#:~:text=Why%20you%20shouldn't%20use,previous%20function%20is%20garbage%20collected.
The short answer is because arrow function is recreated every time, which will hurt the performance.
One solution proposed from this post is to wrapped in a useCallback hook, with empty array. And when I change to this, the eslint warning really disappear.
const Example = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
However, there is also another opinion saying that overusing useCallback will eventually slowdown the performance due to the overheads of useCallback. One example is here: https://kentcdodds.com/blog/usememo-and-usecallback
This is making me really confused? So for Functional Components, when dealing with inline function handler, should I just write the arrow function (ignore the eslint) or always wrap it in a useCallback ???
The short answer is because arrow function is recreated every time, which will hurt the performance.
This is a common misconception. The arrow function is recreated every time either way (although with useCallback subsequent ones may be thrown away immediately). What useCallback does is make it possible for the child component you use the callback on to not re-render if it's memoized.
Let's look at the misconception first. Consider the useCallback call:
const increment = useCallback(() => setCounter(counter => counter + 1), []);
That's executed like this:
Evaluate the first argument, () => setCounter(counter => counter + 1), creating a function
Evaluate the second argument, [], creating an array
Call useCallback with those two arguments, get back a function
Compare with what you have if you don't use useCallback:
const increment = () => setCounter(counter => counter + 1);
That's much simpler: Create the function. It doesn't then have to do #2 and #3 above.
Let's move on to what useCallback actually does that's useful. Let's look at where the callback is used:
<Button onClick={increment} />
Now, suppose Button is memoized with React.memo or similar. If increment changes every time your component renders, then Button has to re-render every time your component changes; it can't be reused between renders. But if increment is stable between renders (because you used useCallback with an empty array), the memoized result of calling Button can be reused, it doesn't have to be called again.
Here's an example:
const { useState, useCallback } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const increment = useCallback(
() => setCount(count => count + 1),
[]
);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Note that clicking the button in ComponentA always calls Button again, but clicking the button in ComponentB doesn't.
When do you want to do that? That's largely up to you, but it probably makes sense when your component's state will change frequently in ways that don't affect the contents of increment and thus don't affect Button and if Button has to do significant work when rendered. Button probably doesn't, but other child components may.
For instance, the useCallback in my previous example is probably pointless if you use count as the text of the button, since that means Button has to re-render regardless:
const { useState, useCallback } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
<Button onClick={increment}>{count}</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const increment = useCallback(
() => setCount(count => count + 1),
[]
);
return (
<div>
<Button onClick={increment}>{count}</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Also note that useCallback isn't free, it impacts the code in the callback. Look at the code in the callbacks in ComponentA and ComponentB in the examples. ComponentA (which doesn't use useCallback) can use the value of count that it closes over (within limits!), () => setCount(count + 1). But the one in ComponentB always has to use the callback form of the setter, () => setCount(count => count + 1). That's because if you keep using the first increment you create, the count it closes over will be stale — you'd see the count go to 1, but never further.
A final note: If you're re-rendering a component so often that creating and throwing away the various functions you're passing to useCallback or useMemo may be causing too much memory churn (a rare situation), you can avoid it by using a ref. Let's look at updating ComponentB to using a ref instead of useCallback:
const incrementRef = useRef(null);
if (!incrementRef.current /* || yourDependenciesForItChange*/) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
incrementRef.current = () => setCount(count => count + 1);
}
const increment = incrementRef.current;
That only creates the increment function once (in that example, since we don't have any dependencies), it doesn't create and throw away functions like using useCallback does. It works because the initial value of the ref is null, and then the first time the component function is called, we see that it's null, create the function, and put it on the ref. So increment is only created once.
That example does recreate the function we pass setCount every time increment is called. It's possible to avoid that, too:
const incrementRef = useRef(null);
if (!incrementRef.current) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const incrementCallback = count => count + 1;
incrementRef.current = () => setCount(incrementCallback);
}
const increment = incrementRef.current;
const { useState, useRef } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
const incrementRef = useRef(null);
if (!incrementRef.current) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const incrementCallback = count => count + 1;
incrementRef.current = () => setCount(incrementCallback);
}
const increment = incrementRef.current;
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
That's really going to 11 in terms of avoiding unnecessary function creation. :-)
It's a rare component that needs even that first level of optimization, much less the second level; but when/if you do, that's how you do it.

ReactJS: Why different values from one state?

So I'm getting into Reactjs with very basic component.
I'm logging out the same state from different functions, but what I'm seeing is the different values.
import React, { useState, useEffect, useRef } from "react";
const Test = props => {
const [count, setCount] = useState(0);
useEffect(()=>{
setInterval(() => {
console.log("count in interval is:", count);
}, 1000);
},[props]);
function btnClick() {
const newCount = count + 1;
setCount(newCount);
console.log("count changed to: ", newCount);
}
return (
<div>
count is {count}
<br></br>
<button onClick={btnClick}>+</button>
</div>
);
};
export default Test;
Output after some clicks and wait, log is:
Test.js:8 count in interval is: 0
Test.js:15 count changed to: 1
Test.js:15 count changed to: 2
Test.js:15 count changed to: 3
Test.js:15 count changed to: 4
(8 rows) Test.js:8 count in interval is: 0
I expect the "count" to be the same in both functions.
Can any one explain this?
Thank so much.
Test only has one setInterval function where count is always 0. Since it's only created during initial render.
It never had another setInterval created because the effect never got triggered with [props] as the dependency.
To have setInterval's count change on every re-render:
Remove the dependency
Return a clean-up function inside the effect
useEffect(
() => {
const t = setInterval(() => {
console.log("count in interval is:", count);
}, 1000);
return () => clearInterval(t); // cleanup on every re-render
}
// no dependency: effect runs on every re-render
);
But the above code will have a warning:
"missing count dependency"
So simply add count as dependency to only run the effect when count changes.
useEffect(
() => {
const t = setInterval(() => {
console.log("count in interval is:", count);
}, 1000);
return () => clearInterval(t); // cleanup "old" setInterval
}
, [count] // ony run effect every time count changes
);
The value of count doesn't change, this is the expected behavior, though not an obvious one.
See, you even declare count as a const count, it is immutable. What is happening instead is that during the first render count gets assigned value of 0. The value of count never changes, what happens instead is that component Test is called each time you change the state, and function useState assigns different values to the constant count, which is new constant every time.
So during the first render the value of const count gets captured by closure inside your function that is called by setInterval and the value stays 0 forever.

How to get latest component variable value in 'useEffect' hook?

How to use an updated value of a variable (declared at component scope) in each 'useEffect'?
import React, { useState, useEffect } from 'react';
export default function Count() {
const [count, setCount] = useState(0);
let a = 10;
useEffect(() => {
console.log('after 1st render', a);
a++;
console.log(a);
return () => { console.log('cleanup - on unmount.') }
}, [a]);
useEffect(() => {
console.log('only when count changes', a);
a++;
return () => { console.log('count cleanup', a) }
}, [count, a]);
return <div>
<p>Count : {count}</p>
<button onClick={() => { console.log('at global', a); setCount(count + 1) }}>Click</button>
</div>
}
output
after 1st render 10
11
only when count changes 11
at global 12
count cleanup 12
only when count changes 10
Now, What I don't understand from this output is the last line which outputs the value of 'a' as 10.
Every time a useEffect gets called, it creates a new copy of the function provided, and it also calls the cleanUp function right?.
when I click the button the count changes, the previous cleanUp gets called and clean that 'useEffect' behaviour which sets the value of 'a' from 11 to 12, and then the current 'useEffect' called with value 10. it should print the value 11. ? can anyone please clarify this.
On every render, all Count body executed, therefore changing state via button click will result in calling let a = 10 and resetting the value of a.
In other words, the local variable a lifetime is until the next render.
To get desired behavior, try using a reference with useRef.
useRef returns a mutable ref object whose .current property is
initialized to the passed argument (initialValue). The returned object
will persist for the full lifetime of the component.
const [count, setCount] = useState(0);
const aRef = useRef(10);
let a = 10;
useEffect(() => {
console.log("after 1st render", a);
a++;
aRef.current++;
console.log(a);
return () => {
console.log("cleanup - on unmount.");
};
}, [a]);
useEffect(() => {
console.log("only when count changes", a);
console.log("only when count changes - ref", aRef.current);
a++;
aRef.current++;
return () => {
console.log("count cleanup", a);
};
}, [count, a]);
Will result:
only when count changes
10
only when count changes - ref
12
Read more at uses of useEffect

React hook setState arguments

I would like to know the difference between the following two versions of code. Both versions do the same.
1) Here just the counter variable is used to get the current value
const Counter = () => {
const [counter, setCounter] = useState(0);
return <button onClick={() => setCounter(counter + 1)}>{counter}</button>;
}
2) This version passes a function to setCounter
const Counter = () => {
const [counter, setCounter] = useState(0);
return <button onClick={() => setCounter(c => c + 1)}>{counter}</button>;
}
The official documentation says:
If the new state is computed using the previous state, you can pass a
function to setState. The function will receive the previous value,
and return an updated value.
So what's wrong with the first option? Are there some pitfalls?
With the particular code in your example, you have the previous value in hand, so there isn't much difference. But sometimes you don't. For example, suppose you wanted to have a memoized callback function. Due to the memoization, the value of counter gets locked in when the closure is created, and won't be up to date.
const Counter = () => {
const [counter, setCounter] = useState(0);
// The following function gets created just once, the first time Counter renders.
const onClick = useCallback(() => {
setCounter(c => c + 1); // This works as intended
//setCounter(counter + 1); // This would always set it to 1, never to 2 or more.
}, []); // You could of course add counter to this array, but then you don't get as much benefit from the memoization.
return <button onClick={onClick}>{counter}</button>;
}

Categories

Resources