How To Solve The React Hook Closure Issue? - javascript

import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [count, setCount] = useState(0);
function handleAlertClick() {
return (setTimeout(() => {
alert("You clicked on: " + count);
}, 3000))
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}
I just want to know if this works the way that I think it does, or if there is a better explanation!
Whenever the setState method is called, the state gets a new reference. This means the original state doesn't have a new value, but instead creates a new state with a new value. When we click on the second button, the event handler function captures the reference of the original state.
Even if we click the first button many times, when the alert is displayed it will show the value of the state that the event handler captured its reference.
Is this correct?

The reason the alert shows the outdated value of count is because the callback passed to setTimeout is referencing an outdated value of count captured by the closure. This is usually referred to as a stale-closure.
On the initial render, the anonymous function passed as a callback to setTimeout captures the value of count as 0, and when the button show alert gets clicked the callback gets queued but with the outdated value of count.
In the case above the easiest solution to show the updated value of count in the alert message and fix the stale-closure issue will be to use a ref.
function App() {
const [count, setCount] = useState(0);
const latestValue = useRef(count);
const handleAlertClick = () => {
setTimeout(() => {
alert(`count is: ${latestValue.current}`);
}, 3000);
};
return (
<div>
<p>You clicked {count} times</p>
<button
onClick={() => {
setCount(prev => {
latestValue.current = prev + 1;
return prev + 1;
});
}}
>
Click me
</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}
Working demo in codesandbox
Hooks rely heavily on closures to work, so it very likely that you may bump into problems regarding stale-closures. Here is a nice article on how stale-closures create issues when using react-hooks and demonstrates how to fix some the issue in some situations.

https://dmitripavlutin.com/react-hooks-stale-closures/#32-usestate
function App() {
const [count, setCount] = useState(0);
const handleAlertClick = () => {
setTimeout(() => {
alert(`count is: ${count}`);
}, 3000);
};
return (
<div>
<p>You clicked {count} times</p>
<button
onClick={() => {
setCount((count) => count + 1);
}}
>
Click me
</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}

Related

Is it necessary to use anonymous callback function in onClick handler? React

I'm currently learning the react hooks with an online course.
The instructor passed an anonymous callback function onto the onClick handler
return (
<div className="counter">
<button className="counter-action decrement" onClick={() => decrementScore()}> - </button>
<span className="counter-score">{score}</span>
<button className="counter-action increment" onClick={() => incrementScore()}> + </button>
</div>
);
But I don't understand why the anonymous callback is needed, and why I can't just pass the function by itself.
Following is what I tried and it worked ok without an error.
const Counter = () => {
const [score, setScore] = React.useState(0);
const incrementScore = () => {
setScore(prevScore => prevScore + 1);
}
const decrementScore = () => {
setScore(prevScore => prevScore > 0 ? prevScore - 1 : 0);
}
return (
<div className="counter">
<button className="counter-action decrement" onClick={decrementScore}> - </button>
<span className="counter-score">{score}</span>
<button className="counter-action increment" onClick={incrementScore}> + </button>
</div>
);
}
The additional anonymous callback isn't needed here. Doing it your way is fine.
It would be useful to have such an anonymous callback if:
The function being called was a member of an object, and it needs a this context of the object, and the function isn't bound. For example, the following would look pretty suspicious in a class component:
onClick={this.handleClick}
(see here for many pages on the subject)
You want to make sure the function is called with a particular argument or arguments, and not with others. For example, you might have
<button onClick={() => changeValue(1)}>click1</button>
<button onClick={() => changeValue(2)}>click 2</button>
Or, you might possibly want to omit the default React event that gets passed as the first argument to an event handler. But, in this case, no arguments are used in incrementScore or decrementScore, so it doesn't matter.
Additionally, note that you only need to use the callback form of a state setter, eg:
setScore(prevScore => prevScore + 1);
when the state value that the enclosing function closes over may be stale - if the state setter has been called previously, and the component hasn't re-rendered yet. But for a state that changes only once when a button is clicked, the state value has no chance of being stale. So, if you wanted, in this case, you could simplify
const incrementScore = () => {
setScore(prevScore => prevScore + 1);
}
to
const incrementScore = () => {
setScore(score + 1);
}

I have a button that increment a counter when users click on it, but I want to delay the updating process if users click really fast

I have a button that increments a counter with useState hook when users click on it, but I want to know if there is a way to delay the state updating for 0.5 seconds when users click on the button really fast, then update the counter at once. For example, when users click on the button 1 times each second, the counter will be updated immediately. But if users click more than 3 times in one second, the state will not be updated immediately, and the counter will only be updated when users stop clicking fast. The counter will be updated to the number of clicks during the delay. I tried to use setTimeOut but it did not work. Is there a hook for this?
function App() {
// State to store count value
const [count, setCount] = useState(0);
// Function to increment count by 1
const incrementCount = () => {
// Update state with incremented value
setCount((prev)=>{
return prev+1
});
};
return (
<div className="app">
<button onClick={incrementCount}>Click Here</button>
{count}
</div>
);
}
You need to apply Javascript Throttle function. Debounce is not an ideal solution here because with Debounce even after the first click user will have to wait for some time(delay) before execution happens. What you want is that on the first click counter should be incremented but after that if user clicks too fast it should not happen untill some delay ,that what Throttle function provides.
Also Thing to note that to use Throttle or Debounce in React application you will need an additional hook i.e. useCallback, which will not redfeine the function on every re-render and gives a memoized function.
More on difference between Throttle and Debounce :https://stackoverflow.com/questions/25991367/difference-between-throttling-and-debouncing-a-function#:~:text=Throttle%3A%20the%20original%20function%20will,function%20after%20a%20specified%20period.
Let's look at the code :
import { useState, useCallback } from "react";
function App() {
// State to store count value
const [count, setCount] = useState(0);
// Throttle Function
const throttle = (func, limit = 1000) => {
let inThrottle = null;
return (...args) => {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
};
// Function to increment count by 1
const incrementCount = useCallback(
throttle(() => {
// Update state with incremented value
setCount((prev) => {
return prev + 1;
});
}, 1000),
[]
);
return (
<div className="app">
<button onClick={incrementCount}>Click Here</button>
{count}
</div>
);
}
export default App;
This is pure adhoc implementation. I just tried with two state variable and simple implementation. Basically,
firstly at initial click I'm doing count variable 1 instantly. Then, after each click, it will take 1 second for updating count state.
Then, I put a if block in setTimeout() method which is, if the difference between current count value and previous count value is 1, then the count variable will update. The checking is because, on each click the count variable increasing very fast. So, the condition becomes obstacle for that.
import { useState } from "react";
function App() {
// State to store count value
const [count, setCount] = useState(0);
const [prevCount, setPrevCount] = useState(0);
// Function to increment count by 1
const incrementCount = () => {
setPrevCount(count);
if(count === 0) setCount(1);
setTimeout(() => {
if(count - prevCount === 1) {
setCount(prev => prev + 1);
}
}, 1000);
};
return (
<div className="app">
<button onClick={incrementCount}>Click Here</button>
{count}
</div>
);
}
export default App;

can't get updated state value inside method

I wanna stop the looping when stopState is true.
The stopState is updated when I click the stop button. but inside the startIncrement method, the stopState is always false.
This is my code:
function App() {
const [num, setNum] = useState(0)
const [stop, setStop] = useState(false)
function Delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function startIncrement(){
for(let i=0; i<100; i++){
console.log(stop) // always false even when i click the stop button
if(stop) i = 2000;
setNum(i)
await Delay(1000)
}
}
return (
<main>
<p>stop: {stop ? "true" : "false"}</p>
<p onClick={startIncrement}>{num}</p>
<button onClick={() => setStop(true)}>stop</button>
</main>
);
}
Short answer:
In order to stop the previously excuted function, you will have to invoke a `clean up` function.
Detail on how it works:
In react, each render has it's own props and state, and we can explain this in action:
First, you excuted the function startIncrement(), at the time of excution, and at that perticular render phase, the value of stop is false;
Every time the num value changes, the page renders, and it just keep going... (excute below code, you can see console prints "renders!")
Then you click setStop(true) button, and at this particular render, stop === true.
However, these two steps involves two different renders, and each renders' state and everything else (props, effect...), does not affect each other, therefore, your stop value in the function never changes.
Here's an alternative to achieve the same:
export default function App() {
const [num, setNum] = useState(0)
const [stop, setStop] = useState(null)
console.log("renders!")
useEffect(() => {
const run = setInterval(() => {
if(stop === false && num < 100) setNum(num + 1);
}, 1000);
return () => clearInterval(run) // clean up function
}, [num, stop]);
return (
<main>
<p>stop: {stop ? "true" : "false"}</p>
<p>{num}</p>
<button onClick={() => setStop(false)}>start</button>
<button onClick={() => setStop(true)}>stop</button>
</main>
);
}
The clean up function in useEffect can be seen as a "undo" function.
Sandbox here, you can read more about side effect from this post in Dan Abramov's blog

When to use functional update form of useState() hook, eg. setX(x=>x+1)

Normally when we need to update a state in a functional component, we do something like this:
function Example() {
const [count, setCount] = React.useState(0);
return (<div><p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>);
}
When and why will we ever need to use the functional update form?
function Example() {
const [count, setCount] = React.useState(0);
return (<div><p>You clicked {count} times</p>
<button onClick={() => setCount(c=>c + 1)}>
Click me
</button>
</div>);
}
Use the function form when the setter may close over an old state value.
For example, if an async request is initiated, and you want to update state after that's done, the request that was made will have scope of the state as it was at the beginning of the request, which may not be the same as the most up-to-date render state.
You may also need to use the function form if the same state value was just updated, eg
setValue(value + 1);
// complicated logic here
if (someCondition) {
setValue(value => value + 1);
}
because the second call of setValue closes over an old value.
State Updates May Be Asynchronous:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
useState is the same as setState in this condition.
You can see the different when call set state twice:
<button
onClick={() => {
setCount(count + 1);
setCount(count + 1);
}}
></button>;
<button
onClick={() => {
setCount(c => (c + 1));
setCount(c => (c + 1));
}}
></button>;
There are other use cases too. For example, when you call useState inside an effect. If new state is dependent on old state, this might cause an infinite loop.
useEffect(() => {
setCounter(counter + 1);
}, [counter]);
You can avoid this by using functional updates:
useEffect(() => {
setCounter(old => old + 1);
}, []);
According to this: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
The the functional update form make sure that the previous state that you take reference from is the latest / finalized version when there might be multiple setState hook (which is asynchronous) called (for example, if the user spam click on the button)
And also due to its async nature, the state will not be updated right away within a function, for e.g:
func() {
console.log(counter) // counter = 1
setCounter(counter => counter + 1) // counter = 1
console.log(counter) // counter = 1
}
The functional update form also allows the update function to be passed to its children while still having access to the parent’s state.
function MyButton(props){
// return <button onClick={()=>props.onClick(count+1)}>+1</button>; // error as count is not exposed here
return <button onClick={()=>props.onClick(n=>(n+1))}>+1</button>;
}
function Example() {
const [count, setCount] = React.useState(0);
return (<div><p>Counter: {count}</p>
<MyButton onClick={setCount}/>
</div>);
}
ReactDOM.render(<Example/>,document.querySelector("div"));

What is the use of functional syntax of setState in react functional components? [duplicate]

This question already has answers here:
Why React useState with functional update form is needed?
(5 answers)
When are functional updates required for computations involving previous state?
(1 answer)
Closed 2 years ago.
we are talking about functional components having useState
lets say
const [age, setAge] = useState(0)
now let us say while updating age I have to use the previous age
React docs mention something called FUNCTIONAL UPDATES where you can pass a function and the argument to that will be the previous value of the state, eg.
setState((previousAge) => previousAge + 1)
why do I need to do this when I can just do
setState(previousAge + 1)
what are the benefits of using functional setState,
I know in class-based components there was something called batching of state updates in the functional way,
but I can't find anything like that in functional components documentation.
They are not the same, if your update depends on a previous value found in the state, then you should use the functional form. If you don't use the functional form in this case then your code will break sometime.
Why does it break and when
React functional components are just closures, the state value that you have in the closure might be outdated - what does this mean is that the value inside the closure does not match the value that is in React state for that component, this could happen in the following cases:
1- async operations (In this example click slow add, and then click multiple times on the add button, you will later see that the state was reseted to what was inside the closure when the slow add button was clicked)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick={() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
2- When you call the update function multiple times in the same closure
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
Problems might occur depending on how fast/often your setter gets called.
If you are using the simple way by getting the value from the closure, subsequent calls between two renders might not have the desired effect.
A simple example:
function App() {
const [counter, setCounter] = useState(0);
const incWithClosure = () => {
setCounter(counter + 1);
};
const incWithUpdate = () => {
setCounter(oldCounter => oldCounter + 1);
};
return (<>
<button onClick={_ => { incWithClosure(); incWithClosure(); }}>
Increment twice using incWithClosure
</button>
<button onClick={_ => { incWithUpdate(); incWithUpdate(); }}>
Increment twice using incWithUpdate
</button>
<p>{counter}</p>
</>);
}
Both buttons calls one of the increment methods twice.
But we observe:
The first button will increment the counter only by 1
The second button will increment the counter by 2, which is probably the desired outcome.
When can this happen?
Obviously, if incWithClosure is called multiple times immediately after each other
If asynchronous tasks are involved, this can easily happen (see below)
Perhaps, if React has much work to do, its scheduling algorithms may decide to handle multiple very fast clicks using the same event handler
Example with asynchronous work (simulating loading a resource):
function App() {
const [counter, setCounter] = useState(0);
const incWithClosureDelayed = () => {
setTimeout(() => {
setCounter(counter + 1);
}, 1000);
};
const incWithUpdateDelayed = () => {
setTimeout(() => {
setCounter((oldCounter) => oldCounter + 1);
}, 1000);
};
return (
<>
<button onClick={(_) => incWithClosureDelayed()}>
Increment slowly using incWithClosure
</button>
<button onClick={(_) => incWithUpdateDelayed()}>
Increment slowly using incWithUpdate
</button>
<p>{counter}</p>
</>
);
}
Click on the first button twice (within one second) and observe that the counter gets incremented only by 1. The second button has the correct behavior.
Because if you don't you will find at some point that you get an old value for age. The problem is, sometimes what you suggest will work. But sometimes it will not. It may not break in your current code today but it may break in a different code you wrote a few weeks ago or your current code a few months from now.
The symptom is really, really weird. You can print the value of the variable inside a jsx component using the {x} syntax and later print the same variable using console.log after rendering the jsx component (not before) and find that the console.log value is stale - the console.log that happens after the render can somehow have older value than the render.
So the actual value of state variables may not always work correctly in regular code - they are only designed to return the latest value in a render. For this reason the callback mechanism in a state setter was implemented to allow you to get the latest value of a state variable in regular code outside of a render.

Categories