Which is the right way to detect first render in a react component
Asked Answered
F

7

24

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>
Fro answered 26/11, 2020 at 19:1 Comment(9)
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.Humbert
@Humbert 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?Fro
I suggest the Approach 1, useRef is enough.Bookworm
refs are used because they won't trigger an additional render when they get updated unlike state. also @Humbert 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 usesLouvar
@Louvar 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.Fro
What about declaring let firstRender = true outside App function so right before it's definition, and in useEffect if (firstRender) { firstRender = false; //Do something}...Ardith
@Ardith 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 workLouvar
@Louvar yeah but seems that it is App and that it will never be umount/remount.... of course this was an ugly and fast solutionArdith
@Humbert although it's generally true that components should have low sensitivity to state change order, there are special cases. For example, you might want your root component to perform some special processing on first load, e.g.: loading data from localStorage. There are of course other ways to implement this, like for example by having your App component never rerender and instead give it a child component called StatefulApp that hooks into your reducer or whatever you are using for top-level state. Would be interesting to know if anyone has opinions about the best practice for this.Pallua
A
27

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;
}
Arvad answered 10/2, 2021 at 15:9 Comment(2)
Getting error:React Hook "useRef" is called in function "isFirstRender" that is neither a React function component nor a custom React Hook function.Exclusion
@Exclusion The name of your function has to start with use. Otherwise, React won't know it is supposed to be a hook.Arvad
L
14
const firstRender = useRef(true);

useEffect(() => {
  if (firstRender.current) {
    firstRender.current = false;
    return;
  }
  doSomething();
});
Laser answered 4/12, 2021 at 14:53 Comment(0)
J
12

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.

Jeffry answered 26/11, 2020 at 19:55 Comment(0)
F
10

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.
Fractious answered 13/5, 2023 at 12:37 Comment(0)
R
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(), [])
                                   ^^
Rachelrachele answered 9/1, 2023 at 0:33 Comment(0)
C
0

You can use the following custom hook:

import { useEffect, useState } from "react"

export const useIsFirstRender = () => {
    const [isFirst, setIsFirst] = useState(true);
    useEffect(() => { setIsFirst(false) }, [])
    return isFirst
}

Then use it as the following

import isFirstRender=useIsFirstRender();
console.log('Is first render:',isFirstRender)
Cinthiacintron answered 20/12, 2023 at 12:10 Comment(0)
L
0

You can store a value in sessionStorage and check if the value is present in sessionStorage. if it is present, it is not first render. This is not specific to react.

if(sessionStorage.getItem('is_first_render') === null) sessionStorage.setItem('is_first_render', 'somevalue')

Lakieshalakin answered 3/1 at 10:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.