Hacer que el gancho React useEffect no se ejecute en el renderizado inicial

Resuelto Yangshun Tay asked hace 6 años • 20 respuestas

Según los documentos:

componentDidUpdate()se invoca inmediatamente después de que se produce la actualización. Este método no se llama para el renderizado inicial.

Podemos usar el nuevo useEffect()gancho para simular componentDidUpdate(), pero parece que useEffect()se ejecuta después de cada renderizado, incluso la primera vez. ¿Cómo consigo que no se ejecute en el renderizado inicial?

Como puede ver en el ejemplo siguiente, componentDidUpdateFunctionse imprime durante el renderizado inicial pero componentDidUpdateClassno se imprimió durante el renderizado inicial.

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/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

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

Yangshun Tay avatar Nov 12 '18 05:11 Yangshun Tay
Aceptado

Podemos usar el useRefgancho para almacenar cualquier valor mutable que queramos , por lo que podríamos usarlo para realizar un seguimiento de si es la primera vez que useEffectse ejecuta la función.

Si queremos que el efecto se ejecute en la misma fase que componentDidUpdatelo hace, podemos usarlo useLayoutEffecten su lugar.

Ejemplo

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/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

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

Tholle avatar Nov 11 '2018 22:11 Tholle

Puedes convertirlo en un gancho personalizado (nueva página de documentación: Reutilización de lógica con ganchos personalizados ) , así:

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;

Ejemplo de uso:

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 avatar Sep 15 '2019 05:09 Mehdi Dehghani

Hice un useFirstRendergancho simple para manejar casos como enfocar una entrada de formulario:

import { useRef, useEffect } from 'react';

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

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

  return firstRender.current;
}

Comienza como true, luego cambia a false, useEffectque solo se ejecuta una vez y nunca más.

En tu componente, úsalo:

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

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

Para tu caso, simplemente usarías if (!firstRender) { ....

Marius Marais avatar Sep 07 '2020 10:09 Marius Marais

Mismo enfoque que la respuesta de Tholle , pero usando useStateen lugar de useRef.

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

...

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

EDITAR

Si bien esto también funciona, implica actualizar el estado, lo que hará que su componente se vuelva a renderizar. Si todas las llamadas de su componente useEffect(y también todas las de sus hijos) tienen una matriz de dependencia, esto no importa. Pero tenga en cuenta que cualquiera useEffectque no tenga una matriz de dependencia ( useEffect(() => {...})se ejecutará nuevamente.

El uso y la actualización useRefno provocarán ninguna repetición.

Luigi avatar Aug 08 '2021 17:08 Luigi