UseEffect and Where to Put State

tbraeck

Tate Braeckel

Posted on February 28, 2023

UseEffect and Where to Put State

Side-effects and state in React can be difficult topics to tackle on their own, but when I was faced with trying to wrap my brain around using both together for my code challenge recently- I felt kind of lost.

However, now that I have had time to examine the code further and look through notes I took during my discussion with my assessor after the fact, I feel more confident in my understandings about the UseEffect Hook(side-effects) and the UseState Hook(state) in React.

I will use the code from the code challenge as an example to explain when state should be utilized within a UseEffect Hook and when it should be outside UseEffect in the functional component.

The code challenge was to create a counter that, on mount, would begin counting up by 1 each second. There also needed to be 3 buttons: a play button, a reverse button, and a pause button.

My initial instinct was to focus on the functionality of the buttons, creating an onClick event for each, a handler function that would deal with state and state value changes along the way.

This can be seen with the code snippet below:

return (
        <div>
            <p>{count}</p>
            <button type='click' onClick={handlePlay}>Play</button>
            <button type='click' onClick={handleReverse}>Reverse</button>
            <button type='click' onClick={handlePause}>Pause</button>

        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Yes, the buttons needed this functionality and an event listener, but placing state in the correct place within the overall component was actually more important.

The counter's functionality is in the UseEffect Hook, because it is a side-effect, and is happening asynchronously with the other functionality of the component. The interval starts after the initial rendering of the component and changes only when the state within the dependency array changes. Below is part of the UseEffect Hook content:

useEffect(() => {
        let interval;

        if (isOn) {
            interval = setInterval(() => {
                if (isReverse) {
                    setCount((count) => count - 1)
                } else {
                    setCount((count) => count + 1)
                }
            }, 1000)
Enter fullscreen mode Exit fullscreen mode

I knew that a state and setter function of something like "count, setCount" were needed for the counter=> which had an output on the client side as a

element. My initial inclination was to handle this and the other state outside of the useEffect- most likely in my handler functions- but that was very incorrect.


const [count, setCount] = useState(0)
    const [isOn, setIsOn] = useState(true)
    const [isReverse, setIsReverse] = useState(false)
Enter fullscreen mode Exit fullscreen mode

As seen above, the isOn and isReverse state are wrapped up in conditionals, and those two states are values within the dependency array- meaning when they change, the UseEffect fires again and runs through the conditionals until it lands on an argument that is true in that instance based on state at that time.

The first one, 'isOn' is - being true it runs the setInterval, then if 'ifReverse' is true, the setCount setter function decrements the counter by one. The isOn and isReverse states truthiness determines which conditional is carried out- each holding the setCount setter function and either an increment or decrement of the count state(current state of count).

This could not be handled outside the useEffect because the interval is asynchronous and a side-effect, so changes to it's behavior and state need to live in the UseEffect Hook with it and not in the "pure" function component that it lives in.

The current state of 'count' is actually determined by the state of isOn and isReverse within this nested conditional. No part of this state is needed outside of this Hook.

Also, the isOn and isReverse state values are both booleans (true/ false), so depending on the state of each of these (t/f) that will determine outside the UseEffect how those states can change based on an onClick event and the handler function that event calls.

 function handlePlay() {
        setIsOn(true)
        setIsReverse(false)
    }

    function handleReverse() {
        setIsOn(true)
        setIsReverse(true)
    }

    function handlePause() {
        setIsOn(false)
    }

Enter fullscreen mode Exit fullscreen mode

For instance, the handlePause function is fired when the button is clicked, the state of isOn is set to false and the counter is paused. When the reverse button is clicked it fires the handleReverse function that sets isOn to true and isReverse to true as well, which in the useEffect hook will fire the decrement code:

if (isOn) {
interval = setInterval(() => {
if (isReverse) {
setCount((count) => count - 1)
} else {
setCount((count) => count + 1)

So, after all that, state should live in a useEffect if that states value is determined by the functions/ side-effects within the useEffect, but can be coupled with conditionals and hard-wired state within handler functions to determine the path of the state within the conditionals in the useEffect.

This was definitely a brain-teaser but I found it very interesting to look deeper into useState and useEffect in React. I had not done any code as complicated as this up until this point.

💖 💪 🙅 🚩
tbraeck
Tate Braeckel

Posted on February 28, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024