Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Skip first useEffect when there are multiple useEffects

To restrict useEffect from running on the first render we can do:

  const isFirstRun = useRef(true);
  useEffect (() => {
    if (isFirstRun.current) {
      isFirstRun.current = false;
      return;
    }

    console.log("Effect was run");
  });

According to example here: https://stackoverflow.com/a/53351556/3102993

But what if my component has multiple useEffects, each of which handle a different useState change? I've tried using the isFirstRun.current logic in the other useEffect but since one returns, the other one still runs on the initial render.

Some context:

const Comp = () => {
const [ amount, setAmount ] = useState(props.Item ? Item.Val : 0);
const [ type, setType ] = useState(props.Item ? Item.Type : "Type1");

useEffect(() => {
    props.OnAmountChange(amount);
}, [amount]);

useEffect(() => {
    props.OnTypeChange(type);
}, [type]);

return {
    <>
        // Radio button group for selecting Type
        // Input field for setting Amount
    </>
}
}

The reason I've used separate useEffects for each is because if I do the following, it doesn't update the amount.

useEffect(() => {
    if (amount) {
        props.OnAmountChange(amount);
    } else if (type) {
        props.OnTypeChange(type)
    }
}, [amount, type]);
like image 747
blankface Avatar asked Jul 28 '19 10:07

blankface


People also ask

How do you not execute a useEffect when loading the page for the first time?

We can make the React useEffect callback not run on the first render by creating a ref that keeps track of whether the first render is done. Then we can check the ref's value to see when the first render is done and run the function we want when the first render is done.

Can we use multiple useEffects?

You can have multiple useEffects in your code and this is completely fine! As hooks docs say, you should separate concerns. Multiple hooks rule also applies to useState - you can have multiple useState in one component to separate different part of the state, you don't have to build one complicated state object.

How do you make useEffect run only once?

Side Effect Runs Only Once After Initial Render You do not want to make this API call again. You can pass an empty array as the second argument to the useEffect hook to tackle this use case. useEffect(() => { // Side Effect }, []); In this case, the side effect runs only once after the initial render of the component.


1 Answers

As far as I understand, you need to control the execution of useEffect logic on the first mount and consecutive rerenders. You want to skip the first useEffect. Effects run after the render of the components.

So if you are using this solution:

const isFirstRun = useRef(true);
  useEffect (() => {
    if (isFirstRun.current) {
      isFirstRun.current = false;
      return;
    }

    console.log("Effect was run");
  });
   useEffect (() => {
    // second useEffect
    if(!isFirstRun) {
        console.log("Effect was run");
     }
   
  });

So in this case, once isFirstRun ref is set to false, for all the consecutive effects the value of isFirstRun becomes false and hence all will run.

What you can do is, use something like a useMount custom Hook which can tell you whether it is the first render or a consecutive rerender. Here is the example code:

const {useState} = React

function useMounted() {
  const [isMounted, setIsMounted] = useState(false)


  React.useEffect(() => {
    setIsMounted(true)
  }, [])
  return isMounted
}

function App() {


  const [valueFirst, setValueFirst] = useState(0)
  const [valueSecond, setValueSecond] = useState(0)

  const isMounted = useMounted()

  //1st effect which should run whenever valueFirst change except
  //first time
  React.useEffect(() => {
    if (isMounted) {
      console.log("valueFirst ran")
    }

  }, [valueFirst])


  //2nd effect which should run whenever valueFirst change except
  //first time
  React.useEffect(() => {
    if (isMounted) {
      console.log("valueSecond ran")
    }

  }, [valueSecond])

  return ( <
    div >
    <
    span > {
      valueFirst
    } < /span> <
    button onClick = {
      () => {
        setValueFirst((c) => c + 1)
      }
    } >
    Trigger valueFirstEffect < /button> <
    span > {
      valueSecond
    } < /span> <
    button onClick = {
      () => {
        setValueSecond((c) => c + 1)
      }
    } >
    Trigger valueSecondEffect < /button>

    <
    /div>
  )
}

ReactDOM.render( < App / > , document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

I hope it helps !!

like image 79
simbathesailor Avatar answered Sep 22 '22 09:09

simbathesailor