Updating state with functions inside. Should we use It? - reactjs

I've watched a video with React best practices. The author said that updating state with functions inside is a best practice because if we write this statement two times, it would still work properly (example #2).
However, I can't understand, in which situations it helps us to avoid bugs. Maybe you have some examples...
export default function Counter({ initCount }) {
const [count, setCount] = useState(initCount);
useEffect(() => {
console.log(count);
}, [count]);
const incrementCount = () => {
// example #1
// setCount(count + 1);
// setCount(count + 1);
// count was incremented by 1
// example #2
setCount((currCount) => currCount + 1);
setCount((currCount) => currCount + 1);
// count was incremented by 2, as expected
};
const decrementCount = () => {
setCount((currCount) => currCount - 1);
setCount((currCount) => currCount - 1);
};
return (
<div>
<button onClick={decrementCount}>-</button>
{count}
<button onClick={incrementCount}>+</button>
</div>
);
}

I'm updating state using function inside only if I need to avoid unnecessary dependency in useEffect or useCallback
For example:
const [count, setCount] = useState(0);
useEffect(() => {
// useEffect depends on count now and we have to add `count` to dependency list
setCount(count + 1)
}, [count]);
useEffect(() => {
// no dependencies
setCount(prevCount => prevCount + 1)
}, [])
So I think you should use the function for setState only if your changes depend on previous value of state

Related

updated state value are not updated inside function in react

react state updated values are shown in use Effect but inside function only shown an old value.
const [counter, setCounter] = useState(0);
I am trying to update the counter value inside a set interval function
const handleIncrease = () => {
clearInterval(intervalval);
if (counter < 10) {
let increment = setInterval(() => {
console.log("isCounterContinue#handleIncrease", counter);
setCounter((prev) => prev + 1);
}, 1000);
setIntervalval(increment);
}
};
inside useEffect updated values are shown. but inside function handleIncrease only show the old value
Basically, I am trying to do counter value is not increase when it's more than 30.
code Link : https://codesandbox.io/s/bold-cdn-zzbs2?file=/src/App.js
handleIncrease is only called when a button is clicked, with the current state. There is nothing to update in the click handler. I think what you are really after is accessing the updated counter state in the interval's callback, which "ticks" once every second. Or more accurately, respond to the isCounterContinue state toggling false to stop the interval when a limit is hit.
Use a ref to hold a reference to the interval timer and set/clear using this instead of a state that goes stale in enclosures.
const Timer = () => {
const [counter, setCounter] = useState(0);
const intervalRef = useRef();
useEffect(() => {
console.log({ counter });
if (counter >= 5) {
clearInterval(intervalRef.current);
}
}, [counter]);
const handleIncrease = () => {
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
};
const handleDecrease = () => {
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setCounter((prev) => prev - 1);
}, 1000);
};
const handleStop = () => {
clearInterval(intervalRef.current);
};
return (
<>
<div>{counter}</div>
<div>
<button onClick={handleDecrease}>Decrease</button>
<button onClick={handleStop}>Stop</button>
<button onClick={handleIncrease}>Increase</button>
</div>
</>
);
};
Suggestion
The increment/decrement handlers are basically identical other than what they add to the count. Use a curried function to handle both cases by closing over an incrementing value. Since the "stop" handler shares logic to clear the interval, use the fact that 0 is a falsey value and only restart an interval timer for truthy (i.e. non-zero) number values and use one single handler for all three buttons.
const handleIncrease = (val) => () => {
clearInterval(intervalRef.current);
if (val) {
intervalRef.current = setInterval(() => {
setCounter((prev) => prev + val);
}, 1000);
}
};
...
<button onClick={handleIncrease(-1)}>Decrease</button>
<button onClick={handleIncrease(0)}>Stop</button>
<button onClick={handleIncrease(1)}>Increase</button>

react hooks setInterval,Why can it be modified for the first time

Why is it that the correct count value can be obtained in setinterval after the first click, and then the transformation does not occur again?
import React, { useEffect, useState } from 'react';
const Demo1 = () => {
let [count, setCount] = useState(1);
const onCountClick = () => {
count += 1;
setCount(count);
};
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1000);
}, []);
console.log(count);
return <button onClick={() => onCountClick()}>test</button>;
};
You are directly modifying the state. Instead do this:
setCount(count++)
React doen't really handle setInterval that smoothly, you have to remember that when you put it in componentDidMount (useEffect with an empty dependencies' array), it builds its callback with the current values, then never updates.
Instead, put it inside componentDidUpdate (useEffect with relevant dependencies), so that it could have a chance to update. It boils down to actually clearing the old interval and building a new one.
const Demo1 = () => {
let [count, setCount] = useState(1);
let [intervalId, setIntervalId] = useState(null);
const onCountClick = () => {
count += 1;
setCount(count);
};
useEffect(() => {
setIntervalId(setInterval(() => {
console.log(count);
}, 1000));
}, []);
useEffect(() => {
clearInterval(intervalId);
setIntervalId(setInterval(() => {
console.log(count);
}, 1000));
}, [count]);
console.log(count);
return <button onClick={() => onCountClick()}>test</button>;
};
The first thing is that changing the value of state directly like count += 1 is a bad approach, instead use setCount(count + 1) and you cannot console.log any value in the return statement instead use {count} to display the value on the screen instead of console.
The following code will increment the value of count on every click instance
const [count, setCount] = useState(1);
const onCountClick = () => {
// count += 1;
setCount(count + 1);
};
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1000);
});
return (
<div className="App">
<button onClick={() => onCountClick()}>test</button>;
</div>
);

State is not reflecting immediately in react hooks

I am learning hooks. I am trying to update state but it's not reflecting immediately.
Here is my code.
const Test = (props) => {
const [score , setScore] = useState(0);
const [count , setCount] = useState(0);
const [total, setTotal] = useState(0);
const playerBallClick= ()=> {
setCount(count+1);
setScore(Math.floor(Math.random() * 10));
setTotal(total + score);
}
return (
<div>
<button onClick={playerBallClick}>Ball</button>
{/* <p>Total score is - {totalscore}</p> */}
</div>
)
}
How can I update Total immediately onclick on Ball button.
You can use useEffect hook like so,
useEffect(() => {
setTotal(total + score);
}, [count]);
So everytime count state changes, this hook will be called updating your total state.
Score is a stale closure try the following instead:
const playerBallClick = () => {
setCount(count + 1);
const newScore = Math.floor(Math.random() * 10);
setScore(newScore);
setTotal(total => total + newScore);
};

React UseEffect Example Not Working Reason

Why does this not work as a normal one second counter?
function UseEffectBugCounter() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1);
console.log(count);
}, 1000);
return () => clearInterval(intervalId);
}, []);
return <div>The count is: {count}</div>;
}
Example: https://codesandbox.io/s/sparkling-rgb-6ebcp
-Is it because of stale closures?
or
-Is it because the count is a state variable and the component would be re-rendered after the state update so a new interval will be created creating some sort of loop?
or
-Is it something else?
I'm looking for a why this occurs in this answer if possible, there are a few different articles stating why it doesn't work (as per above). But none have been able to provide a good argument so far.
You can use callback for set state to use latest counter value:
setCount(count => (count + 1));
You may need to add the dependency for count in useEffect. Currently useEffect is only called on the mount and is not called after that (i.e when the count value changes).
So it always says 0 because useEffect is executed only once ( on mount ) and that time the count value is set to 0. And thus it every time it logs 0 on setInterval due to closure.
I have updated the code sandbox to find the reason and meaningful logs. Here is the sandbox link: https://codesandbox.io/s/admiring-thompson-uz2xe
How to find closure value: You can check the logs and traverse through prototype object to find [[Scopes]] and you will get the values as seen in the below screenshot:
This would work:
React.useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1);
console.log(count);
}, 1000);
return () => clearInterval(intervalId);
}, [count]);
You can check this doc: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
You can read this as well: You can read this as well: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Hope this helps!
If you removed second params for useEffect your application will be rendered always if u have some change in state, but this is bad practice. You need in second params choose for wich parametrs you need watch ...
Example with choosen params:
React.useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1);
console.log(count);
}, 1000);
return () => clearInterval(intervalId);
}[count]);
Without
React.useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1);
console.log(count);
}, 1000);
return () => clearInterval(intervalId);
});
Because you didn't add count to useEffect's dependences, then inside the effect count always is 0.
You should use useReducer to resolve your problem:
function UseEffectBugCounter() {
const [count, dispatchCount] = React.useReducer((state, { type }) => {
switch(type) {
case 'inc':
return state + 1
default:
return state
}
}, 0);
React.useEffect(() => {
const intervalId = setInterval(() => {
dispatchCount({ type: 'inc'})
}, 1000);
return () => clearInterval(intervalId);
}, []);
return <div>The count is: {count}</div>;
}
You need to pass count instead of blank array in useEffect
function UseEffectBugCounter() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1);
console.log(count);
}, 1000);
return () => clearInterval(intervalId);
},[count]);
return <div>The count is: {count}</div>;
}

Is there any difference between rendering a functional component with and without hooks?

Just tried some react-hooks and got some questions.
Consider this functional component with react-hooks:
const Counter = (props) => {
console.log("Counter component");
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount(count + 1);
}
const handleDecrease = () => {
setCount(count - 1);
}
return (
<div>
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
<p>{count}</p>
</div>
)
}
It logged everytime I clicked the "+" or "-".
Does it means that every handlers inside this component(or say, function) are redeclared and reassigned to a variable? If it did, won't it cause some performance issues?
To me the functional component with hooks seems like a huge render method of a classical component like this:
class Counter extends React.Component {
state = {
count: 0,
}
render() {
const handleIncrease = () => {
this.setState({ count: this.state.count + 1 });
}
const handleDecrease = () => {
this.setState({ count: this.state.count - 1 });
}
return (
<div>
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
<p>{count}</p>
</div>
)
}
}
which I think nobody will do this.
Did I have misunderstandings of the React's render mechanism or it's just not the best practice when using the functional component with react-hooks?
Although in functional components functions are recreated on every render, the performance cost of it much less compared to the benefits.
You can refer this post for more details: Performance penalty of creating handlers on every render
However you can still optimise so that functions are not recreated on every render using useCallback or useReducer(depending on whether your updates are complex or not)
const Counter = (props) => {
console.log("Counter component");
const [count, setCount] = useState(0);
const handleIncrease = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [])
const handleDecrease = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [])
return (
<div>
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
<p>{count}</p>
</div>
)
}
In the above example the functions are re-created only on the initial render and using the state update callback we can update the state and avoid closure issues

Resources