Los hooks nos permiten externalizar, encapsular y reutilizar lógica. Yendo a lo básico, podemos recordar que los componentes de clase tenían métodos concretos y propios extendidos de React.Component
mientras que los componentes funcionales no. Son sólo una función con lo que nosotros le incluyamos, de manera que no tenemos ni constructor en el que definir this.state = {}
ni método setState()
ni nada, así que lo primero que deberíamos aprender es cómo utilizar un state en un funcional.
El hook de estado
Lo primero que habría que hacer es importarlo. Ten en cuenta que en las últimas versiones no hace falta importar React en cada componente, así que sería algo así:
import { useState } from 'react';
¿Por qué useState
? porque la norma dice que si es un hook empieza por «use», y como básicamente sirve para gestionar un state
, no hay más que hablar.
Usarlo es casi igual de sencillo que importarlo. Para ilustrarlo voy a usar el ejemplo de siempre: un contador.
const [count, setCount] = useState(0);
¿Identificas la sintaxis? El método useState
retorna un array con dos valores, de manera que podemos desestructurarlo. Ya sé que al principio puede parecer magia, pero qué bien se lee, ¿verdad? Y para terminar, el 0
que le estamos mandando como parámetro es el valor inicial de ese estado.
Un ejemplo rápido pero completo:
import { useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
return (
<>
<h1>You made {count} clicks</h1>
<button onClick={() => setCount(count + 1)}>Increase</button>
</>
);
};
Y listo. Tal y como yo lo veo, es más fácil que el this.setState
de las clases. Lo malo es que siempre hay un pero, y en este caso es importante: no hay callback y los setters son asíncronos, así que hay que tener cuidado con esto.
Siguiendo el ejemplo anterior, vamos a introducir el siguiente hook, que también es de los más usados: useEffect
.
El hook de «efecto»
Imagino que todo empieza a parecer casi tan confuso como interesante 😂 . Con useEffect
podemos enterarnos de cuando hay cambios. Esto es muy interesante, porque sólo con el método useEffect
podemos hacer lo mismo que con componentDidMount
, componentDidUpdate
y componentWillUnmount
. Sé que al principio es confuso, pero dale una oportunidad y verás que tiene bastante sentido.
Vamos a crear otro state
en el que mantendremos un booleano indicando si el contador ha sido incrementado o no, simplemente así:
const [increased, setIncreased] = useState(false);
Y el componente no retornará siempre el mismo h1
, le diremos que si no se ha incrementado nunca muestre otra cosa:
{ increased ?
<h1>You made {count} clicks</h1> :
<h1>Not clicked yet</h1>
}
Ahora bien, ¿cómo actualizamos el valor de increased
? Podríamos hacerlo «escuchando» los cambios en counter
. Y ahí aparece useEffect
, que recibe una función en el primer parámetro y un array de dependencias, estas dependencias son las que, en caso de cambiar, dispararán la función del primer parámetro.
useEffect(() => {
setIncreased(true);
}, [count]);
Ahora deberíamos tener un código más o menos así:
import { useState, useEffect } from "react";
const App = () => {
const [count, setCount] = useState(0);
const [increased, setIncreased] = useState(false);
useEffect(() => {
setIncreased(true);
}, [count]);
return (
<>
{ increased ?
<h1>You made {count} clicks</h1> :
<h1>Not clicked yet</h1>
}
<button onClick={() => setCount(count + 1)}>Increase</button>
</>
);
};
Esto sería casi correcto y está a punto de funcionar. ¿Por qué no funciona? Porque en cuanto el componente se monta hace el primer setCount
, que rebota en ese useEffect
y setea increased
a true
.
Ahora deberíamos conocer si ese efecto está ejecutandose por el montado o por un cambio «real» en count, lo que nos lleva diréctamente al siguiente hook: useRef
.
El hook de referencia
Y con este vamos a ir terminando, no desesperes. El hook useRef
sirve para mantener un valor durante la vida del componente, es decir, que no se reiniciará en cada render. Su uso más habitual no es este que voy a darle ahora, pero es muy adecuado y viene bien para enlazar estos 3 hooks. Vamos allá:
Lo primero sería crear una referencia en la que almacenaremos un booleano con el que sabemos si estamos en la primera ejecución o no.
const firstUpdate = React.useRef(true);
El segundo paso sería utilizar de nuevo el hook useEffect
para actualizar el valor de firstUpdate
cuando fuera necesario.
useEffect(() => {
if(firstUpdate.current) {
firstUpdate.current = false;
return;
}
}, []);
// Fíjate en el array de dependencias. Como ves está vacío,
// eso significa que tendrá un comportamiento similar a componentDidMount
// y sólo se ejecutará en el primer renderizado, lo cual ya nos va bien.
Como ves, en firstUpdate
no se guarda directamente el valor, si no que está en su propiedad current
.
Para terminar sólo habría que actualizar el efecto anterior para que sólo modifique el estado increased
en caso de que no sea el primer render.
useEffect(() => {
if(!firstUpdate.current){
setIncreased(true);
}
}, [count]);
Y ahora sí, ya estaría terminado y para ejemplificarlo en condiciones un Codepen en el que se puede ver el resultado y el código final.
See the Pen
by Josep Viciana (@emmgfx)
on CodePen.
Photo by Anete Lusina from Pexels