Home > Blockchain >  React state change best practices: useEffect vs callback
React state change best practices: useEffect vs callback

Time:02-12

I have a question regarding best practices when using useEffect and when to use it to handle state changes.

For example let's say I have a button that will send an email on the 5th click (pseudo code as I didn't really test to see if it works). What's better according to React best practices.

Here I am using useEffect to handle the count state change:

import React, { useState, useEffect } from 'react'
export default () => {
    const [count, setCount] = useState(0)
    useEffect(() => {
        if (count === 5) {
            sendEmail()
            setCount(0)
        }
    }, [count])

    return (
        <div>
            <input
                type="button"
                onClick={() => {
                    setCount((count) => count   1)
                }}
            ></input>
        </div>
    )    
}

Here is the same functionality with a callback:

import React, { useState } from 'react'
export default () => {
    const [count, setCount] = useState(0)
   
    return (
        <div>
            <input
                type="button"
                onClick={() => {
                    if(count   1 === 5) {
                        sendEmail()
                        setCount(0)
                        return
                    }
                    setCount(count=>count 1)
                }}
            ></input>
        </div>
    )    
}

The argument for using useEffect, at least as I see it, is to react to state changes. However if I want to check other state in that useEffect block or other dependencies, then I have to add it to the dependency array and it's in danger of re-running even though I'm only interested in running it in response to the count changing (one piece of state). In this example it isn't too egregious but in more advanced situations it seems that you have to add a bunch of conditionals in the useEffect block if you only want it to react to one piece of state change. if(x,y,z) {run count logic}

The argument for the callback is that it's readable and will only be called on the button being pressed however it is more imperative than declarative and not as REACTive as useEffect. Furthermore with the setCount being async it might create a race condition (not in this case but in more advanced cases).

Also I can use useEffect to run code when a component has first rendered but if it has logic that touches dependencies I have to add them to the dependency array according to my linter and Dan Abramov but then I run the chance of running it multiple times unless I add a bunch of conditionals in the body of useEffect (as I mentioned above). In short I may want to run the useEffect on one piece of state change and not when any of the dependencies change.

I hope that makes sense.

An example of the useEffect issue with conditionals is if I want to send the email if some localState variable === "whatever". Once I add the localState variable into the dep array, then it will run whenever it changes even if I only want to run it when specific state (count) changes. The only way around this I see is adding logic on the useEffect to exit early but it starts to get complicated and error prone.

CodePudding user response:

In your kind of situation, it's better to just call sendmail within your click function (option 2) to avoid unnecessary rerendering.

With regards to question of "race condition", instead of using setCount(0), you can always use a ref to store the count. You can use ref to store the count since you do not need to trigger a rerender when your count changes.

Otherwise, instead of resetting to 0, you can always do

if ((count % 5) === 0) {
 sendmail()
}

CodePudding user response:

I would go with option 1. If you want to run other effects off of some state you can have a separate useEffect block for that, so your count block only runs when the count state changes.

  • Related