Dante1021
Dante1021

Reputation: 374

How to stop initial render for useEffect hook

Earlier I had a Class component, so I didn't face any issues while using lifecycle methods, but after converting to useEffect hooks, I am facing the initial render issue which I don't want to happen.

Class

componentDidMount() {
    this.setState({
        patchVal:this.props.patchTaskVal,
        startTime:this.props.patchStartTime,
        setEndTime:this.props.patchEndTime
    })
}
    
componentDidUpdate(prevProps) {
    if (prevProps.patchTaskVal !== this.props.patchTaskVal) {
        this.callValidation()
    }

    if (prevProps.closeTask !== this.props.closeTask) {
        this.setState({
            showValue:false,
            progressValue:[],
            startTime:new Date(),
            setEndTime:""
        })
    }

    if (prevProps.patchStartTime !== this.props.patchStartTime || prevProps.endTime !== this.props.endTime && this.props.endTime !== "") {
        this.setState({
            startTime:this.props.patchStartTime,
            setEndTime:parseInt(this.props.endTime)
        })
    }
}

Functional

const [patchTaskVal, setPatchTaskVal]=useState(/*initial value */) 
const [startTime, setStartTime]=useState()
const [endTime, setEndTime] = useState()
    
**// I want only this useEffect to run on the initial render**
useEffect(() => {
    setPatchTaskVal(props.patchTaskVal)
    ...//set other states
}, [])
    
useEffect(() => {
    callValidation() 
}, [props.patchTaskVal])
    
useEffect(() => {
    //setShowValue...
}, [props.closeTask])
    
useEffect(() => {
    if (props.endTime != "") {
        // set states...
    }
}, [props.patchStartTime,props.endTime])

Here I am facing an issue where all the useEffects are running on the initial render, Please suggest a solution for this so that only the first useEffect will run on the initial render and all other useEffects will run according to its dependency prop values.

Upvotes: 0

Views: 5406

Answers (4)

Alex Turuta
Alex Turuta

Reputation: 11

If you compare the functional and the class component you can notice that there is one part missing - previous props.

Functional component does not have previous props in scope, but you can save them yourself with a small trick: save them to reference so it will not impact you render cycle.

Since now you have the previous props and the current props you can apply the same logic you did for class component.

import React, { useRef, useEffect, useState } from "react";

default function App() {
  const [input, setInput] = useState("");
  const [commitInput, setCommitInput] = useState("");

  return (
    <>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={() => setCommitInput(input)}>apply</button>
      <Child test={commitInput} />
    </>
  );
}

function Child(props) {
  const prev = useRef(props.test);
  useEffect(() => {
    if (prev.current !== props.test) {
      alert("only when changes");
    }
  }, [props.test]);

  return <div>{props.test}</div>;
}

Upvotes: 1

Tushar Shahi
Tushar Shahi

Reputation: 20376

You basically need a ref which will tell you whether this is the first render on not. Refs values persist over rerenders. You can start with a truthy value and toggle it to false after the first render (using a useEffect with an empty array[]). Based on that you can run your desired code.

You can also put the whole thing in a custom hook:

import { useEffect, useRef } from "react";

const useOnUpdate = (callback, deps) => {
  const isFirst = useRef(true);
  useEffect(() => {
    if (!isFirst.current) {
      callback();
    }
  }, deps);

  useEffect(() => {
    isFirst.current = false;
  }, []);
};

export default useOnUpdate;

You can call this hook in your component like :

  useOnUpdate(() => {
    console.log(prop);
  }, [prop]);

In the hook: After the initial render, both useEffects run. But when the first effect runs the value of the isFirst.current is true. So the callback is not called. The second useEffect also runs and sets isFirst.current to false. Now in subsequent renders only the first useEffect run (when dependencies change), and isFirst.current is false now so callback is executed.

The order of the two useEffects is very important here. Otherwise, in the useEffect with deps, isFirst.current will be true even after the first render.

Link

Upvotes: 1

HsuTingHuan
HsuTingHuan

Reputation: 625

Hope my understanding is right about your question.
Why not just add a if statement to check the state is not undefined or default value

useEffect( ()=> {
  if (props.patchTaskVal) {
    callValidation() 
  }
},[props.patchTaskVal])

useEffect( ()=>{
  if (props.closeTask) {
    //setShowValue...  
  }
},[props.closeTask])

useEffect( ()=>{
    if(props.patchStartTime){
    // set states...
    }
    
    if(props.endTime){
    // set states...
    }
},[props.patchStartTime,props.endTime]

And according your class component,

this.setState({
patchVal:this.props.patchTaskVal,
startTime:this.props.patchStartTime,
setEndTime:this.props.patchEndTime
})

The function component should map props to component's state. Like this

const [patchTaskVal, setPatchTaskVal]=useState(props.patchTaskVal) 
const [startTime, setStartTime]=useState(props.patchStartTime)
const [endTime, setEndTime] = useState(props.patchEndTime)

Upvotes: 0

ShoaibAh
ShoaibAh

Reputation: 197

try this...

let init = true;
useEffect( ()=>{
if(init) {

    setPatchTaskVal(props.patchTaskVal)
init = false;
    ...//set other states}
}, [])

useEffect( ()=> {

    !init && callValidation() 
},[props.patchTaskVal])

useEffect( ()=>{
//!init && setShowValue...
},[props.closeTask])

useEffect( ()=>{
    if(props.endTime!="" && !init){
    // set states...
    }
},[props.patchStartTime,props.endTime])

Upvotes: 0

Related Questions