329

According to the docs:

componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.

We can use the new useEffect() hook to simulate componentDidUpdate(), but it seems like useEffect() is being ran after every render, even the first time. How do I get it to not run on initial render?

As you can see in the example below, componentDidUpdateFunction is printed during the initial render but componentDidUpdateClass was not printed during the initial render.

function ComponentDidUpdateFunction() {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {
    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

class ComponentDidUpdateClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidUpdate() {
    console.log("componentDidUpdateClass");
  }

  render() {
    return (
      <div>
        <p>componentDidUpdateClass: {this.state.count} times</p>
        <button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        >
          Click Me
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <ComponentDidUpdateFunction />
    <ComponentDidUpdateClass />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>
Yangshun Tay
  • 49,270
  • 33
  • 114
  • 141
  • 1
    may I ask what is the use case when it makes sense to do something based on number of renders and not an explicit state variable like `count`? – Aprillion Nov 15 '18 at 15:20
  • @Aprillion, in my case change the content of an H2 that has a text that need to change after the list of item, is empty and was even different at beginning. The same list is also empty at beginning before data fetch from API so with normal conditional rendering based on array length the inititial value is overrrided – Carmine Tambascia Nov 05 '21 at 14:32

18 Answers18

296

We can use the useRef hook to store any mutable value we like, so we could use that to keep track of if it's the first time the useEffect function is being run.

If we want the effect to run in the same phase that componentDidUpdate does, we can use useLayoutEffect instead.

Example

const { useState, useRef, useLayoutEffect } = React;

function ComponentDidUpdateFunction() {
  const [count, setCount] = useState(0);

  const firstUpdate = useRef(true);
  useLayoutEffect(() => {
    if (firstUpdate.current) {
      firstUpdate.current = false;
      return;
    }

    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

ReactDOM.render(
  <ComponentDidUpdateFunction />,
  document.getElementById("app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 12
    I tried to replace `useRef` with `useState`, but using the setter triggered a re-render, which is not happening when assigning to `firstUpdate.current` so I guess this is the only nice way :) – Aprillion Nov 15 '18 at 15:16
  • 6
    Could someone explain why use layout effect if we're not mutating or measuring the DOM? – ZenVentzi Mar 06 '19 at 12:45
  • 6
    @ZenVentzi It's not necessary in this example, but the question was how to mimic `componentDidUpdate` with hooks, so that's why I used it. – Tholle Mar 06 '19 at 12:52
  • 1
    I created a custom hook [here](https://stackoverflow.com/a/53254028/1541563) based on this answer. Thanks for the implementation! – Patrick Roberts Nov 05 '19 at 20:09
  • Quick recap, the [`componentDidUpdate`](https://reactjs.org/docs/react-component.html#componentdidupdate) lifecycle method is invoked during each render, except the first one. The [`componentDidMount`](https://reactjs.org/docs/react-component.html#componentdidmount) is only invoked once, after the first render. All answers here use names which may confuse some readers. – Advena Jan 12 '23 at 08:01
  • With the recent React18 that calls usEffect twice in development mode, you'd want to add a cleanUp function, so that preventing running on first render continues to work: `useLayoutEffect(() => { if (firstUpdate.current) { firstUpdate.current = false; return; } console.log("componentDidUpdateFunction"); return () => (firstUpdate.current = true); });` – hane Smitter Jul 24 '23 at 12:33
167

You can turn it into custom hook (new documentation page: Reusing Logic with Custom Hooks), like so:

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

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        if (didMount.current) func();
        else didMount.current = true;
    }, deps);
}

export default useDidMountEffect;

Usage example:

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

import useDidMountEffect from '../path/to/useDidMountEffect';

const MyComponent = (props) => {    
    const [state, setState] = useState({
        key: false
    });    

    useEffect(() => {
        // you know what is this, don't you?
    }, []);

    useDidMountEffect(() => {
        // react please run me if 'key' changes, but not on initial render
    }, [state.key]);    

    return (
        <div>
             ...
        </div>
    );
}
// ...
Mehdi Dehghani
  • 10,970
  • 6
  • 59
  • 64
  • 6
    This approach throws warnings saying that dependency list is not an array literal. – theprogrammer Oct 26 '19 at 05:57
  • 1
    I use this hook in my projects and I didn't see any warning, could you provide more info? – Mehdi Dehghani May 04 '20 at 15:50
  • 2
    @vsync You're thinking about a different case where you want to run an effect once on initial render and never again – Programming Guy May 21 '20 at 03:13
  • 2
    @vsync In the notes section of https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects it specifically says "If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument." This matches observed behaviour for me. – Programming Guy May 22 '20 at 09:58
  • 1
    I also receive the warnings for dependency list is not an array literal and missing dependency: 'func'. The linter rule noted for both is react-hooks/exhaustive-deps – plong0 Feb 23 '21 at 21:36
  • @plong0 Okay, I tested the mentioned code (in my answer) and I didn't get any warning, I think it is related to react version that you using or your dependency (`state.key` in my answer), so I need more info in order to give you better answer. – Mehdi Dehghani Feb 24 '21 at 05:22
  • No need to import `React` in your hook file – gogaz Jun 03 '21 at 10:33
  • he forgot do add: `[]` around deps... should be something like `[deps]`... but it doesnt work for me – Carlos Eduardo Olivera Filho Sep 07 '21 at 21:59
  • @CarlosEduardoOliveraFilho I didn't forgot, it is not the case. when you using this hook you should pass the dependencies as array, just like `useEffect` itself. write your code using `useEffect`, if everything works then simple change the `useEffect` to `useDidMountEffect` and you are good to go. – Mehdi Dehghani Sep 08 '21 at 03:22
  • @Mehdi oh, okay, youre right, im sorry, deps received as a parameter to your custom hook is already an array thats why u dont need the [ ]. Thanks! – Carlos Eduardo Olivera Filho Sep 09 '21 at 07:35
  • There is absolutely no reason for the empty `useEffect()` to be there – fullStackChris Jan 08 '22 at 15:16
  • 3
    You just need `return func()` to send the return value to `useEffect`, in case it's a destructor (ie, cleanup function). – skot May 18 '22 at 17:50
  • 1
    for ```TypeScript``` code : ```func``` is of type ```React.EffectCallback``` and ```deps``` is of type ```React.DependencyList``` – Safi Habhab Jun 16 '22 at 06:49
  • @MehdiDehghani these are the ESLint warning being referred to that I see as well: `ESLint: React Hook useEffect has a missing dependency: 'func'. Either include it or remove the dependency array. If 'func' changes too often, find the parent component that defines it and wrap that definition in useCallback.(react-hooks/exhaustive-deps) ESLint: React Hook useEffect was passed a dependency list that is not an array literal. This means we can't statically verify whether you've passed the correct dependencies.(react-hooks/exhaustive-deps)` – Muttface Jan 04 '23 at 17:31
  • @Muttface That's because the linter is not clever enough to bypass the rule for our specific code, you can disable the rule, e.g: `/* eslint-disable-next-line react-hooks/exhaustive-deps */` or move the code of `useDidMountEffect` inside your component and then use `useCallback` or other solutions in order to make ESLint happy. for more details take a look @ https://stackoverflow.com/q/57111029/3367974 and [Is it safe to omit functions from the list of dependencies?](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies), _I disabled the rule btw_ – Mehdi Dehghani Jan 05 '23 at 04:41
  • Note that React strictmode in development will render the component twice on the initial mount. – Caleb Liu Feb 01 '23 at 00:47
74

I made a simple useFirstRender hook to handle cases like focussing a form input:

import { useRef, useEffect } from 'react';

export function useFirstRender() {
  const firstRender = useRef(true);

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

  return firstRender.current;
}

It starts out as true, then switches to false in the useEffect, which only runs once, and never again.

In your component, use it:

const firstRender = useFirstRender();
const phoneNumberRef = useRef(null);

useEffect(() => {
  if (firstRender || errors.phoneNumber) {
    phoneNumberRef.current.focus();
  }
}, [firstRender, errors.phoneNumber]);

For your case, you would just use if (!firstRender) { ....

Marius Marais
  • 956
  • 6
  • 7
  • 2
    If you add the `firstRender` to the array of dependencies in the useEffect, this will run twice (the first time, and when firstRender is set to false) when mounting. I removed it from the dependencies in my code and it worked. – Rafael Duarte Apr 13 '22 at 10:54
  • @RafaelDuarte I don't think it would. As far as I know, React wont trigger re-renders when a Ref updates. It would do, if firstRender would be a state. Am I wrong? Edit: Oh, but possibly it re-renders, when a hook result changes.... – Mario Eis Jul 06 '22 at 08:19
  • From memory, it'll re-render. That's what `if`s are for though :) – Marius Marais Jul 07 '22 at 10:10
  • Because Booleans are primitives in ES and passed as a values. So useEffect doesn’t know it’s ref. – Dima Vishnyakov Apr 12 '23 at 21:37
17

Same approach as Tholle's answer, but using useState instead of useRef.

const [skipCount, setSkipCount] = useState(true);

...

useEffect(() => {
    if (skipCount) setSkipCount(false);
    if (!skipCount) runYourFunction();
}, [dependencies])

EDIT

While this also works, it involves updating state which will cause your component to re-render. If all your component's useEffect calls (and also all of its children's) have a dependency array, this doesn't matter. But keep in mind that any useEffect without a dependency array (useEffect(() => {...}) will be run again.

Using and updating useRef will not cause any re-renders.

Luigi
  • 403
  • 3
  • 8
10

@ravi, yours doesn't call the passed-in unmount function. Here's a version that's a little more complete:

/**
 * Identical to React.useEffect, except that it never runs on mount. This is
 * the equivalent of the componentDidUpdate lifecycle function.
 *
 * @param {function:function} effect - A useEffect effect.
 * @param {array} [dependencies] - useEffect dependency list.
 */
export const useEffectExceptOnMount = (effect, dependencies) => {
  const mounted = React.useRef(false);
  React.useEffect(() => {
    if (mounted.current) {
      const unmount = effect();
      return () => unmount && unmount();
    } else {
      mounted.current = true;
    }
  }, dependencies);

  // Reset on unmount for the next mount.
  React.useEffect(() => {
    return () => mounted.current = false;
  }, []);
};
Whatabrain
  • 238
  • 4
  • 7
  • Hello @Whatabrain, how to use this custom hook on passing non-depdendency list? Not an empty which would be the same as componentDidmount but something like `useEffect(() => {...});` – Drex Aug 04 '20 at 02:20
  • 1
    @KevDing Should be as simple as omitting the `dependencies` parameter when you call it. – Whatabrain Aug 05 '20 at 15:27
  • You can lose the second `useEffect`. It's not doing anything. That ref you're resetting is going straight in the garbage. If there's a "next mount" it will happen to a different instance of the hook with a `mounted` ref all its own, initialized to false. – skot May 16 '22 at 02:38
  • And ```const unmount = effect(); return () => unmount && unmount();``` can be simplified to ```return effect()``` – skot May 16 '22 at 02:42
  • @skot I wrote the second effect for completeness, but in the next version of React, multiple mounts and unmounts will be possible, making it more necessary. Good catch on the unmount simplification. You're right! – Whatabrain May 17 '22 at 15:28
  • @Whatabrain is that true about the next version? I'd be very curious to learn about that... sounds like a radical change in the paradigm. Do you have a link to docs or an announcement? – skot Jul 26 '22 at 15:13
  • @skot It's part of React 18 in strict mode -- https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors – Whatabrain Jul 27 '22 at 19:45
  • @Whatabrain Okay, yes, I stand corrected! Components can now be remounted and retain their state. Which means you're right, we should reset that ref, because an effect that doesn't want to run on mount probably doesn't want to run on remount either. Nice... I learned something today. Thanks! – skot Jul 28 '22 at 21:09
  • I would do `return () => {mounted.current = false};` to avoid typescript errors such as `Type '() => boolean' is not assignable to type 'Destructor'` – Paintoshi Feb 27 '23 at 08:27
5
function useEffectAfterMount(effect, deps) {
  const isMounted = useRef(false);

  useEffect(() => {
    if (isMounted.current) return effect();
    else isMounted.current = true;
  }, deps);

  // reset on unmount; in React 18, components can mount again
  useEffect(() => {
    isMounted.current = false;
  });
}

We need to return what comes back from effect(), because it might be a cleanup function. But we don't need to determine if it is or not. Just pass it on and let useEffect figure it out.

In an earlier version of this post I said resetting the ref (isMounted.current = false) wasn't necessary. But in React 18 it is, because components can remount with their previous state (thanks @Whatabrain).

skot
  • 428
  • 5
  • 10
3

a simple way is to create a let, out of your component and set in to true.

then say if its true set it to false then return (stop) the useEffect function

like that:


    import { useEffect} from 'react';
    //your let must be out of component to avoid re-evaluation 
    
    let isFirst = true
    
    function App() {
      useEffect(() => {
          if(isFirst){
            isFirst = false
            return
          }
    
        //your code that don't want to execute at first time
      },[])
      return (
        <div>
            <p>its simple huh...</p>
        </div>
      );
    }

its Similar to @Carmine Tambasciabs solution but without using state :) ‍‍‍‍‍‍ ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Rman__
  • 135
  • 1
  • 12
3

I thought creating a custom hook would be overkill and I didn't want to muddle my component's readability by using the useLayoutEffect hook for something unrelated to layouts, so, in my case, I simply checked to see if the value of my stateful variable selectedItem that triggers the useEffect callback is its original value in order to determine if it's the initial render:

export default function MyComponent(props) {
    const [selectedItem, setSelectedItem] = useState(null);

    useEffect(() => {
        if(!selectedItem) return; // If selected item is its initial value (null), don't continue
        
        //... This will not happen on initial render

    }, [selectedItem]);

    // ...

}
A__
  • 1,616
  • 2
  • 19
  • 33
  • And what if this particular state variable changes to this value again at some point throughout the component's lifecycle ? This is a case scenario where someone knows 100% percent that will never happen,because as a useEffect dependency,its purpose is to control the side-effects of state change. – Vaggelis Jun 22 '22 at 15:20
  • If you are asking "what if `selectedItem` becomes `null` again," then yes, you need to either A) make sure it never gets set to `null` again, or B) initialize `useState()` with a value other than `null`, something you know it will never be set to again (i.e., `-1`). – A__ Jun 22 '22 at 16:33
  • 1
    Only this works for me. in my case i gave `value > 0` – Atal Shrivastava Feb 14 '23 at 12:17
3

You can use custom hook to run use effect after mount.

const useEffectAfterMount = (cb, dependencies) => {
  const mounted = useRef(true);

  useEffect(() => {
    if (!mounted.current) {
      return cb();
    }
    mounted.current = false;
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};

Here is the typescript version:

const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
  const mounted = useRef(true);

  useEffect(() => {
    if (!mounted.current) {
      return cb();
    }
    mounted.current = false;
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Foyez
  • 334
  • 3
  • 9
2

This is the best implementation I've created so far using typescript. Basically, the idea is the same, using the Ref but I'm also considering the callback returned by useEffect to perform cleanup on component unmount.

import {
  useRef,
  EffectCallback,
  DependencyList,
  useEffect
} from 'react';

/**
 * @param effect 
 * @param dependencies
 *  
 */
export default function useNoInitialEffect(
  effect: EffectCallback,
  dependencies?: DependencyList
) {
  //Preserving the true by default as initial render cycle
  const initialRender = useRef(true);

  useEffect(() => {
    let effectReturns: void | (() => void) = () => {};

    // Updating the ref to false on the first render, causing
    // subsequent render to execute the effect
    if (initialRender.current) {
      initialRender.current = false;
    } else {
      effectReturns = effect();
    }

    // Preserving and allowing the Destructor returned by the effect
    // to execute on component unmount and perform cleanup if
    // required.
    if (effectReturns && typeof effectReturns === 'function') {
      return effectReturns;
    } 
    return undefined;
  }, dependencies);
}

You can simply use it, as usual as you use the useEffect hook but this time, it won't run on the initial render. Here is how you can use this hook.

useNoInitialEffect(() => {
  // perform something, returning callback is supported
}, [a, b]);

If you use ESLint and want to use the react-hooks/exhaustive-deps rule for this custom hook:

{
  "rules": {
    // ...
    "react-hooks/exhaustive-deps": ["warn", {
      "additionalHooks": "useNoInitialEffect"
    }]
  }
}
Spikatrix
  • 20,225
  • 7
  • 37
  • 83
Kiran Maniya
  • 8,453
  • 9
  • 58
  • 81
  • Do you need all that logic around the return value? Can't you just `return effect()`? – skot May 16 '22 at 00:13
1

@MehdiDehghani, your solution work perfectly fine, one addition you have to do is on unmount, reset the didMount.current value to false. When to try to use this custom hook somewhere else, you don't get cache value.

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

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        let unmount;
        if (didMount.current) unmount = func();
        else didMount.current = true;

        return () => {
            didMount.current = false;
            unmount && unmount();
        }
    }, deps);
}

export default useDidMountEffect;
Whatabrain
  • 238
  • 4
  • 7
  • 3
    I'm not sure this is necessary, since if the component does end up unmounting, because if it does remount, didMount will already be re-initialized to `false`. – Cameron Yick Apr 29 '20 at 14:37
  • @CameronYick it's necessary because when using Fast Refresh the cleanup function will run, but the ref value won't get cleared. This results in glitches while developing - but in production it would be fine. – andreialecu Apr 13 '21 at 07:49
  • I think this is going to set `didMount.current` to false every time the deps change, and thus never fire the effect! Am I wrong? That `didMount` reset, if we want it at all, would need to live in a separate `useEffect` whose deps are `[]` – skot May 16 '22 at 01:01
  • Yeah, what I'm seeing when I use this code is the effect never fires at all. – skot May 16 '22 at 01:09
1

Simplified implementation

import { useRef, useEffect } from 'react';

function MyComp(props) {

  const firstRender = useRef(true);

  useEffect(() => {
    if (firstRender.current) {
      firstRender.current = false;
    } else {
      myProp = 'some val';
    };

  }, [props.myProp])


  return (
    <div>
      ...
    </div>
  )

}
shahul01
  • 113
  • 2
  • 7
1

Recently I need to run the first effect on the very first render cause I initialize some stores in it and don't want the app to be rendered without this effect being executed at least one time so I ended up with a new hook usePreffect :) cause it runs effect pre-render

/**
 * Same as useEffect but runs the very first effect synchronously on the first render.
 * Useful for state initializing effects
 */
export const usePreffect = (effect: EffectCallback, deps: any[]): void => {
  const isFirstEffectRun = useRef(true);
  // Run the effect synchronously on the first render and save its cleanup function
  const firstCleanUp = useMemo(() => effect(), []);

  useEffect(() => {
    // Skip the first run and return the previously saved cleanup function
    if (isFirstEffectRun.current) {
      isFirstEffectRun.current = false;
      return firstCleanUp;
    }
    return effect();
  }, deps);
};
zamuka
  • 796
  • 2
  • 9
  • 21
0

For people who are having trouble with React 18 strict mode calling the useeffect on the initial render twice, try this:

// The init variable is necessary if your state is an object/array, because the == operator compares the references, not the actual values.
const init = []; 
const [state, setState] = useState(init);
const dummyState = useRef(init);

useEffect(() => {
  // Compare the old state with the new state
  if (dummyState.current == state) {
    // This means that the component is mounting
  } else {
    // This means that the component updated.
    dummyState.current = state;
  }
}, [state]);

Works in development mode...

function App() {
  const init = []; 
  const [state, setState] = React.useState(init);
  const dummyState = React.useRef(init);

  React.useEffect(() => {
    if (dummyState.current == state) {
      console.log('mount');
    } else {
      console.log('update');
      dummyState.current = state;
    }
  }, [state]);
  
  return (
    <button onClick={() => setState([...state, Math.random()])}>Update state </button>
  );
}

ReactDOM.createRoot(document.getElementById("app")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

<div id="app"></div>

And in production.

function App() {
  const init = []; 
  const [state, setState] = React.useState(init);
  const dummyState = React.useRef(init);

  React.useEffect(() => {
    if (dummyState.current == state) {
      console.log('mount');
    } else {
      console.log('update');
      dummyState.current = state;
    }
  }, [state]);
  return (
    <button onClick={() => setState([...state, Math.random()])}>Update state </button>
    );
}

ReactDOM.createRoot(document.getElementById("app")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

<div id="app"></div>
Caleb Liu
  • 627
  • 5
  • 18
  • This is when you depend on the `[state]` and it's the correct, but when one needs the opposite, to make React `useEffect` to do something specific only on initial render (`[]`), e.g. setting some flag, then the following could be more appropriate: https://stackoverflow.com/a/76242764/996010 – Hrvoje Golcic May 13 '23 at 16:18
-1

If you want to skip the first render, you can create a state "firstRenderDone" and set it to true in the useEffect with empty dependecy list (that works like a didMount). Then, in your other useEffect, you can check if the first render was already done before doing something.

const [firstRenderDone, setFirstRenderDone] = useState(false);

//useEffect with empty dependecy list (that works like a componentDidMount)
useEffect(() => {
  setFirstRenderDone(true);
}, []);

// your other useEffect (that works as componetDidUpdate)
useEffect(() => {
  if(firstRenderDone){
    console.log("componentDidUpdateFunction");
  }
}, [firstRenderDone]);
  • I used this, I don't see what's wrong with this, if someone could explain why negative points? – Gishas Apr 04 '22 at 09:43
  • This outputs "componentDidUpdateFunction" on the second render only, not on any subsequent ones. If you take `firstRenderDone` out of the deps arrray, this will work. – skot May 18 '22 at 18:05
  • 1
    But it's not as tidy as the `useRef`-based solutions because your first render immediately triggers the second one. So you get a `componentDidUpdate` event right away, but all that's actually updating is the variable you're using to avoid triggering an update right away. – skot May 18 '22 at 18:14
-1

All previous are good, but this can be achieved in a simplier way considering that the action in useEffect can be "skipped" placing an if condition(or any other ) that is basically not run first time, and still with the dependency.

For example I had the case of :

  1. Load data from an API but my title has to be "Loading" till the date were not there, so I have an array, tours that is empty at beginning and show the text "Showing"
  2. Have a component rendered with different information from those API.
  3. The user can delete one by one those info, even all making the tour array empty again as the beginning but this time the API fetch is been already done
  4. Once the tour list is empty by deleting then show another title.

so my "solution" was to create another useState to create a boolean value that change only after the data fetch making another condition in useEffect true in order to run another function that also depend on the tour length.

useEffect(() => {
  if (isTitle) {
    changeTitle(newTitle)
  }else{
    isSetTitle(true)
  }
}, [tours])

here my App.js

import React, { useState, useEffect } from 'react'
import Loading from './Loading'
import Tours from './Tours'

const url = 'API url'

let newTours

function App() {
  const [loading, setLoading ] = useState(true)
  const [tours, setTours] = useState([])
  const [isTitle, isSetTitle] = useState(false)
  const [title, setTitle] = useState("Our Tours")

  const newTitle = "Tours are empty"

  const removeTours = (id) => {
    newTours = tours.filter(tour => ( tour.id !== id))

    return setTours(newTours)
  }

  const changeTitle = (title) =>{
    if(tours.length === 0 && loading === false){
      setTitle(title)
    }
  }

const fetchTours = async () => {
  setLoading(true)

  try {
    const response = await fetch(url)
    const tours = await response.json()
    setLoading(false)
    setTours(tours)
  }catch(error) {
    setLoading(false)
    console.log(error)
  }  
}


useEffect(()=>{
  fetchTours()
},[])

useEffect(() => {
  if (isTitle) {
    changeTitle(newTitle)
  }else{
    isSetTitle(true)
  }
}, [tours])


if(loading){
  return (
    <main>
      <Loading />
    </main>
  )  
}else{
  return ( 

    <main>
      <Tours tours={tours} title={title} changeTitle={changeTitle}           
removeTours={removeTours} />
    </main>
  )  
 }
}



export default App
Carmine Tambascia
  • 1,628
  • 2
  • 18
  • 32
  • I see no difference in this approach, except for the order of the conditions on the if statement. – Luigi Nov 06 '21 at 16:17
  • @Luigi is not really the same as your if check the same variable, so either one or the other is run, In my use case first if change the title variable the second set back the state variable to true – Carmine Tambascia Dec 10 '21 at 22:03
  • that's what I mean with the order changed. you check if you've set it before, and if you have, you run your function. mine first checks if i've haven't set it yet, and then sets it. otherwise, it runs my function (in your case, changeTitle). – Luigi Dec 12 '21 at 01:18
  • 1
    @Luigi I have tried your approach once I had this use case and wasn't working, then I came up with what I wrote above and again I check 2 different variable. At the moment I am over other thins, but I will explain it better why I had to check 2 different variable not the same one. Also there isn't need to downgrade others people answer if they aren't clear. I provided an almost full example, so eventually could be an enhancement of yours, but is more and edge case, otherwise I could have simply have used others answers, included yours. – Carmine Tambascia Feb 03 '22 at 06:38
  • I didn't downvote it. Your answer works. – Luigi Feb 10 '22 at 14:05
-1

    const [dojob, setDojob] = useState(false);

    yourfunction(){
        setDojob(true);
    }

    useEffect(()=>{
        if(dojob){
            yourfunction();
            setDojob(false);
        }
    },[dojob]);

  • 2
    See "[answer]" and [Explaining entirely code-based answers](https://meta.stackoverflow.com/q/392712/128421)". While this might be technically correct, it doesn't explain why it solves the problem or should be the selected answer. We should educate along with helping solve the problem. – the Tin Man Nov 28 '22 at 19:39
  • 2
    Please don't post code-only answers. The main audience, future readers, will be grateful to see explained *why* this answers the question instead of having to infer it from the code. Also, since this is an old question, please explain how it complements *all* other answers. – Gert Arnold Nov 28 '22 at 20:11
-4

Everyone is giving complicated answers, so I'll just leave it her:

This will render on initial load:

useEffect(() => {
    setData(data)
  });

This will not render on initial load

useEffect(() => {
    setData(data)
  }, []);

Just Javascript stuff :shrug:

knaitas
  • 59
  • 3
  • 7
  • 1
    This is not right. The first one will run on every render and the second one will run only once on the initial render. – Ali Nauman Mar 19 '23 at 10:56
  • yes, the second will load on the initial render, but not on the initial load. So when you visit the app it will not fetch the data, unless some changes are made, example state change – knaitas Mar 19 '23 at 16:59