Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useEffect called twice

I know why this happens but want to know what´s the right approach to avoid it. useEffect is called twice. one with data = null and then with "real" data.

const [data, setData] = useState(null);
someServiceToFetchData.then(freshData => setData(freshData))

useEffect(() => {
    console.log("useEffect called");
}, [data]);

should I do something like this everywhere?

useEffect(() => {
    if (data) {
        console.log("useEffect called with real data");
    }
}, [data]);
like image 913
handsome Avatar asked Oct 15 '22 04:10

handsome


2 Answers

As we all know the useEffect is called once on initial render and also on subsequent change of values of dependency array.

In your case, to skip initial execution of useEffect (null case), write a little custom hook like which can be used generically when needed.

Component

import React,  { useState, useEffect } from 'react';
import useEffectSkipInitialRender from "./hook";

const Test = (props) => {
    const [data, setData] = useState(null);

    new Promise((res, rej) => res('my data')).then(freshData => setData(freshData));

    useEffectSkipInitialRender(() => {
        console.log("useEffect called");
    }, [data]);

    return <div>hi</div>
};

export default Test;

Custom hook

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

const useEffectSkipInitialRender = (callback, dataArr) => {
    const [data, setData] = useState(null);
    const isInitialRender = useRef(true);// in react, when refs are changed component dont re-render 

    useEffect(() => {
        if(isInitialRender.current){// skip initial execution of useEffect
            isInitialRender.current = false;// set it to false so subsequent changes of dependency arr will make useEffect to execute
            return;
        }
        return callback();
    }, dataArr);

};

export default useEffectSkipInitialRender;

If you want effect logic to run only when data is not null and data changes, you can write your custom hook like this:

import React,  { useEffect } from 'react';

const useEffectOnDataChange = (callback, dataArr) => {
    const someIsNull = dataArr.some(data => data == null);

    useEffect(() => {
        if (someIsNull) return;
        return callback();
    }, dataArr);

}
like image 142
gdh Avatar answered Oct 23 '22 10:10

gdh


If you want to skip the first render you could use a state flag like this. I find the method below less complicated.

import React, { useState } from 'react';

const Comp = ({dep}) => {
    const [rendered, setRendered] = useState(false);

    useEffect(() => {
        if(rendered){
            // do stuff
        }
        
        if( ! rendered ) {
            setRendered(true);
        }
    }, [dep]);

    // rest of the component code

    return <></>
}
like image 36
Sisir Avatar answered Oct 23 '22 09:10

Sisir