Hacer que el gancho React useEffect no se ejecute en el renderizado inicial
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, componentDidUpdateFunction
se imprime durante el renderizado inicial pero componentDidUpdateClass
no 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>
Podemos usar el useRef
gancho para almacenar cualquier valor mutable que queramos , por lo que podríamos usarlo para realizar un seguimiento de si es la primera vez que useEffect
se ejecuta la función.
Si queremos que el efecto se ejecute en la misma fase que componentDidUpdate
lo hace, podemos usarlo useLayoutEffect
en 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>
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>
);
}
// ...
Hice un useFirstRender
gancho 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
, useEffect
que 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) { ...
.
Mismo enfoque que la respuesta de Tholle , pero usando useState
en 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 useEffect
que no tenga una matriz de dependencia ( useEffect(() => {...})
se ejecutará nuevamente.
El uso y la actualización useRef
no provocarán ninguna repetición.