10

I only want useEffect to run when my dependency list changes, it is also running every time the component is mounted, is there any way to not fire on mount?

You can tell React to skip applying an effect if certain values haven’t changed between re-renders.

I initially thought that meant it shouldn't re-render on subsequent mounts but this question cleared that up.

I am displaying a list of records in a master "page" (react-router), the user can choose a single record and go to the detail page, and then return to the master page - so the master list component is completely unmounted/mounted in that scenario. And every time I load the "master page", I see the data being fetched, I only want this to happen when one of the dependencies changes; these dependencies and the data itself are stored in Redux so they're global.

Can useEffect or another hook be made to only fire when the dependencies change?

const {page, pageSize, search, sorts} = useSelector(getFilters);
const data = useSelector(getData);

useEffect(() => {

  console.log("fetching");
  dispatch(fetchData(page, pageSize, search, sorts));

}, [page, pageSize, search, sorts]);
user210757
  • 5,604
  • 12
  • 54
  • 94

2 Answers2

16

You can't configure it out of the box.

But, a common pattern is to use some isMounted flag like so:

// Is Mounted
const useFetchNotOnMount = () => {
  ...
  const isMounted = useRef(false);

  useEffect(() => {
    if (isMounted.current) {
      console.log('fetching');
      dispatch(fetchData(filters));
    } else {
      isMounted.current = true;
    }
  }, [dispatch, filters]);
};

// Same (Is First Render)
const useFetchNotOnMount = () => {
  ...
  const isFirstRender = useRef(true);

  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender = false;
    } else {
      console.log("fetching");
      dispatch(fetchData(filters));
    }
  }, [dispatch, filters]);
};
Dennis Vash
  • 31,365
  • 5
  • 46
  • 76
0

If you have several useEffect to prevent from running at initially, you can do the following:


export default function App() {
  const mountedRef = useMountedRef();
  const [isLoggedIn, setLoggedIn] = React.useState(false);
  const [anotherOne, setAnotherOne] = React.useState(false);

  React.useEffect(() => {
    if (mountedRef.current) {
      console.log("triggered", isLoggedIn);
    }
  }, [isLoggedIn]);

  React.useEffect(() => {
    if (mountedRef.current) {
      console.log("triggered", anotherOne);
    }
  }, [anotherOne]);

  React.useEffect(() => {
    if (mountedRef.current) {
      console.log("triggered", isLoggedIn, anotherOne);
    }
  }, [anotherOne, isLoggedIn]);

  return (
    <div>
      <button onClick={() => setLoggedIn(true)}>Login</button>
    </div>
  );
}

const useMountedRef = () => {
  const mountedRef = React.useRef(false);

  React.useEffect(() => {
    setTimeout(() => {
      mountedRef.current = true;
    });
  }, []);

  return mountedRef;
};

Demo: https://stackblitz.com/edit/react-eelqp2

One thing important is that you have to use setTimeout to make a reasonable delay to make sure that the ref value is set to true after all initial useEffects.

Ever Dev
  • 825
  • 5
  • 17