15

I have a scenario where I need to detect the first render of a component. Here I have build a small example. Could someone explain to me what is the correct approach?

Why do most of the people suggest to use a ref instead of a plain state.

https://codesandbox.io/s/condescending-burnell-0ex3x?file=/src/App.js

import React, { useState, useRef, useEffect } from "react";
import "./styles.css";

export default function App() {
  const firstRender = useDetectFirstRender();
  const [random, setRandom] = useState("123");
  useEffect(() => {
    if (firstRender) {
      console.log("first");
    } else {
      console.log("second");
    }
  }, [random]);
  return (
    <div className="App">
      <h1>Random Number is {random}</h1>
      <button onClick={() => setRandom(Math.random())}>Change Name</button>
    </div>
  );
}

//Approach 1
// export function useDetectFirstRender() {
//   const firstRender = useRef(true);

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

//   return firstRender.current;
// }

//Approach 2
export function useDetectFirstRender() {
  const [firstRender, setFirstRender] = useState(true);

  useEffect(() => {
    setFirstRender(false);
  }, []);

  return firstRender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Deepak Kumar Padhy
  • 4,128
  • 6
  • 43
  • 79
  • 1
    This is a bad _code-smell_. While components are not meant to be stateless (unlike redux reducers), **components should not care about the order of state refreshes**, for example: a component could be destroyed and recreated in an extant application, or an application might reparent a component and then completely nuke its Redux state without recreating the component. – Dai Nov 26 '20 at 19:10
  • @Dai Sorry, I could not get you fully, I have to this for some kind of animation only after page load. Should we go with `ref` approach? – Deepak Kumar Padhy Nov 26 '20 at 19:14
  • I suggest the Approach 1, useRef is enough. – lissettdm Nov 26 '20 at 19:15
  • 2
    refs are used because they won't trigger an additional render when they get updated unlike state. also @Dai there are plenty of situations where first render can be useful. even if you're right that it's not ideal, that doesn't completely eliminate all legitimate uses – azium Nov 26 '20 at 19:21
  • @azium I benefit now I see with `useRef` is, it saves an extra render after setting the value to false, whereas in-state example, it renders the App component twice. – Deepak Kumar Padhy Nov 26 '20 at 19:32
  • 1
    What about declaring `let firstRender = true` outside App function so right before it's definition, and in useEffect `if (firstRender) { firstRender = false; //Do something}`... – farvilain Nov 26 '20 at 19:43
  • 1
    @farvilain that will only work once per module load, not once per component loading. if the user remounts the component by navigating away and coming back it won't work – azium Nov 26 '20 at 21:42
  • 1
    @azium yeah but seems that it is `App` and that it will never be umount/remount.... of course this was an ugly and fast solution – farvilain Nov 26 '20 at 21:43

5 Answers5

21

You could create a reusable custom hook for that, based on useRef.

function useFirstRender() {
  const ref = useRef(true);
  const firstRender = ref.current;
  ref.current = false;
  return firstRender;
}
Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
  • Getting error:`React Hook "useRef" is called in function "isFirstRender" that is neither a React function component nor a custom React Hook function.` – Ryker Mar 16 '22 at 15:34
  • 4
    @Ryker The name of your function has to start with `use`. Otherwise, React won't know it is supposed to be a hook. – Piotr Siupa Mar 17 '22 at 11:03
11

you can detect and save it by using useMemo or useCallback hook. but here the most preferable is useMemo as it prevent the same rendering again and again.

const firstRender = useMemo(
    () =>console.log('first Render'),
    []
  );

here it will render once and save value in the first Render,so you can use this anywhere where you need.

hafiz adil
  • 109
  • 3
10
const firstRender = useRef(true);

useEffect(() => {
  if (firstRender.current) {
    firstRender.current = false;
    return;
  }
  doSomething();
});
Artem P
  • 5,198
  • 5
  • 40
  • 44
3

When using the StrictMode then it's important to do some useEffect() reset/cleanup as well (return () => { isFirstRender.current = true };). Otherwise the isFirstRender.current will not really reflect what you want, causing the unexpected issues, despite using the [] dependency that suppose to run only on the first render (when component is mounted).

From How to handle the Effect firing twice in development?:

React intentionally remounts your components in development to find bugs like in the last example. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.

const isFirstRender = useRef(true);

useEffect(() => {

  if (isFirstRender.current) {
    // Do something on first render...
  }

  isFirstRender.current = false;

  // ---> StrictMode: The following is REQUIRED to reset/cleanup:
  return () => { isFirstRender.current = true };

}, []); // ---> The `[]` is required, it won't work with `[myDependency]` etc.
Hrvoje Golcic
  • 3,446
  • 1
  • 29
  • 33
1

The useEffect hook takes a second parameter. This second param is an array of variables that the component will check ensure they've changed before re-rendering. However, if that array is empty, the hook is only called once during initial render. This is similar to the useMemo() trick posted previously.

useEffect(() => doSomethingOnce(), [])
                                   ^^
Jeff Lowery
  • 2,492
  • 2
  • 32
  • 40