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>;
}
Related
Hello i am struggling to set dynamic time for settimeout function in react js.
i have long string of key value pair of time and message. i wants to display each message for specific time and loop through whole list.
here is what i am trying, but not working.
const [timer, setTimer] = useState(0)
const [time, setTime] = useState(5000)// this is default value to start which need to update with str time value
const str=[{name:"rammy", time:1000},
{name:"james", time:4000},
{name:"crown", time:2000}]
useEffect(()=>{
const getTime= str[timer].time
setTime(getTime)
},[timer])
//when timer change it should update update time state which will be used to update time for time settime out
function increment() {
useEffect(()=>{
setTimeout(() => {
setTimer((ele)=>ele+1)
}, time);
},[timer])
} // above code is for increment time state on each iteration
function ButtonHandle(){
//setRealString(itr)
increment()
} //button handler for start timer
First of all, you can't put hooks inside functions (other than your component functions). https://reactjs.org/docs/hooks-rules.html
So take the useEffect out of increment()
useEffect(()=>{
increment()
},[timer])
function increment() {
setTimeout(() => {
setTimer((ele)=>ele+1)
}, time);
}
But you also need to clear the timeout. We can return the timeout function to reference it, and clear the time out with a return inside useEffect. Clearing timeouts and intervals in react
useEffect(()=>{
const myTimeout = increment()
return () => {
clearTimeout(myTimeout)
}
},[timer])
function increment() {
return setTimeout(() => {
setTimer((ele) => ele + 1);
}, time);
}
Then we can combine the useEffects which both have a dependancy array of [timer].
useEffect(() => {
const getTime = str[timer].time;
setTime(getTime);
const myTimeout = increment();
return () => {
clearTimeout(myTimeout);
};
}, [timer]);
You don't need to use useEffect to do it. You misunderstood the useEffect usage, it's a react hook to you implement side-effects and you can't use react-hooks inside a function, it should be in the component scope.
I can increment directly from the ButtonHandle function.
// On the index state is necessary in this implementation
const [index, setIndex] = useState(-1)
const guys=[
{name: "rammy", time:1000},
{name: "james", time:4000},
{name: "crown", time:2000}
]
// useCallback memoize the increment function so that it won't
// change the instance and you can use it in the useEffect
// dependency array
const increment = useCallback(() => {
setIndex((i) => i+1)
}, [])
useEffect(() => {
// change the state if the timer is greater than -1
if (index !== -1) {
if (index >= guys.length) {
setIndex(-1);
} else {
setTimeout(() => {
increment();
}, guys[index].time); // <-- you get the time from your array
}
}
}, [index, increment]);
function handleClick(){
//setRealString(itr)
increment()
}
Even though I helped you, I don't know what you're trying to do. This implementation sounds like a code smell. We can help you better if you explain the solution you're trying to do instead of just the peace of code.
You don't need to set the time state, as you already have the time in the array; avoiding unnecessary state changes is good.
I have been experimenting with setInterval. Both the codes were executed in Chrome. Below is my react component
function App() {
let [value, setValue] = useState(0);
useEffect(() => {
const id = setInterval(() => console.log(value), 1000);
return () => clearInterval(id);
}, []);
return (
<div className="App">
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>+</button>
<button onClick={() => setValue(value - 1)}>-</button>
</div>
);
}
The console.log in setTimeout inside useEffect keeps printing 0 no matter how many times you increment or decrement the value via button click.
Following code is being executed in browser (Chrome) console
let value = 0;
setInterval(() => console.log(value), 1000);
value = 3; // can be considered as setValue (sort of)
Now the browser console prints 3 which is expected.
I wonder why there is a difference in behaviour and would appreciate if someone could point out the reason for this. Any other code snippet or link that demonstrate this even better would be great.
You need to pass value as a dependency to your useEffect. Unless you give the list of items that your effect dependent upon in the dependency array, it will keep using the initial value.
useEffect(() => {
const id = setInterval(() => console.log(value), 1000);
return () => clearInterval(id);
}, [value]);
İn your example useEffect only work once when page loaded. This is why you only get console.log(0). If you want to make it when value change you need specify that in useEffect
For example
UseEffect(() =>{ console.log(value) }, [value])
In your example when you click to button, first value sets after that useEffect works and setInternals function triggered after 1s.
While I was studying react hooks, I saw an example of useState function that made of vanilla javascript.
function useState(initVal) {
let _val = initVal
const state = _val
const setState = newVal => {
_val = newVal
}
return [state, setState]
}
const [count, setCount] = useState(1)
console.log(count) // 1
setCount(2)
console.log(count) // 1
Although I declared state as variable, when setState function calls with newValue, the count does not change.
However when the state changes variable to function like below,
function useState(initVal) {
let _val = initVal
const state = () => _val
const setState = newVal => {
_val = newVal
}
return [state, setState]
}
const [count, setCount] = useState(1)
console.log(count()) // 1
setCount(2)
console.log(count()) // 2
the count starts to change.
Is this happening because whenever count function calls, it references changed value of _val by setState function using closure? and How can I explain how react hooks work using closure in javascript?
When you destructure, you are getting new, distinct variables with copies of the data.
// simple explanation
const list = ['dog'];
const [animal] = list;
console.info(animal); // dog
// change the original
list[0] = 'cat';
// output
console.info(list[0], animal); // cat, dog
Even if you refactor as below to use let and update correctly, you will not get the original value as there is no reference to it.
function useState(initVal) {
let state = initVal
const setState = newVal => {
state = newVal;
}
return [state, setState];
}
const [count, setCount] = useState(1);
console.log(count); // 1
setCount(2);
console.log(count); // 1
// simple explanation
const list = ['dog'];
const [animal] = list;
console.info(animal);;
list[0] = 'cat';
console.info(list[0], animal);
Although I declared state as variable
Actually, you didn't. You declared it as a const.
The first returns the value of a variable. Plain and simple. It happens to be 1. The value is copied, you cannot change it. (Obviously you can change what count is set to, but you cannot change 1).
The second returns a function. That function forms a closure over _val. _val isn't evaluated until count() is called. Each time it's called, it gets the value of _val and returns it.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Because you are rebinding it on this line
const state = _val
State becomes a constant frozen with initial value
return [_val, setState]
Change it to this and you will get what you would have expected
const [ change , setChange ] = useState(0);
const funct = () => {
if (change === 100){
return 0;
}
setChange((pre) => {
return pre + 10
});
funct()
}
if I call the function fuct() when any event occur , it will have to call itself (recursive function) until the value of change become 100. But here the function is running infinitely(infinte recursion). This is because the state variable is not changing at every instant of setChange() call.
WHY ?
WHY DID THE STATE IS NOT CHANGED BETWEEN RECURSIVE FUNCTION CALLS ?
WHY ? WHY DID THE STATE IS NOT CHANGED BETWEEN RECURSIVE FUNCTION CALLS ?
Since funct() is triggered via React-based event, state updates are batched.
In order not to batch state update, the trigger should come outside of React-based events, like setInterval().
With that said, here's an example using useEffect() hook with setInterval().
const {useState, useEffect} = React;
const App = (props) => {
const [change, setChange] = useState(0);
useEffect(() => {
const t = setInterval(() => (
setChange((change) => (change + 10))
), 1000);
return () => clearInterval(t);
}, [change]);
return (
<div>{`Change: ${change}`}</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This is because the dispatch function of useState (setChange in this case) is a async operation and doesn't update the state as soon as it is called. You can try this code instead:
const [change, setChange] = useState(0);
const funct = (pre = 0) => {
if (pre === 100) {
return 0;
}
setChange(pre + 10);
funct(pre + 10);
}
Here, the use of setChange can be avoided but, considering your original code might required that, I have provided the solution.
Please make a note! The state will never be useful until the line of codes is left to process. you cannot use useState in that way:
const [mystate, setState] = useState(0);
const getSum = () => {
setState(10);
sum = mystate + 10 //And expect 20? No, you will never get 20 but you will receive 10.
}
always complete your code using let or var and send back the results to the state.
const [ change , setChange ] = useState(0);
let iter = 0;
const funct = () => {
if (iter === 100){
setChange(iter);
}
else{
iter=iter+10
funct();
}
}
You cannot use "change" to return the count value every time the function runs but you can use "iter"! In the end you will have change with total values.
setState/usestate are Asynchronous functions i.e. we can't setState on one line and assume state has changed on the next.
You can try this:
useEffect(() => {
if(change!==0){
funct();
}
}, [change])
const funct = () => {
if (change === 100){
return 0;
}
setChange((pre) => {
return pre + 10
});
}
What difference between direct argument and callback in setState?
I've heard that the react scheduler only correctly exposes component updates when a callback is used. So that they hit the tick 16ms
const [state, setState] = useState(null)
function handle() {
setState(true)
// or
setState(() => true)
}
Using the callback form allows you to use the previous value in state, even if the previous value hasn't been rendered yet. For example:
const [counter, setCounter] = useState(0);
const someHandler = () => {
setCounter(counter + 1);
setCounter(counter + 1);
}
will only end up increasing counter by one, because the counter that's in scope when the handler runs is from a single render. It's like doing setCounter(1); setCounter(1), so counter ends up being 1, not 2.
Using the callback form allows you to use the previous state:
setCounter(counter => counter + 1);
setCounter(counter => counter + 1);
will properly increase counter by 2, not just 1.
Another difference is that using the callback form allows you to put functions into state:
const [fn, setFn] = useState();
const fnToPutInState = () => {
console.log('fn');
};
// ...
setFn(fnToPutInState);
will result in the function being invoked immediately, rather than setting state. The solution is to return the function from a callback instead:
setFn(() => fnToPutInState);