React: useState vs useRef

(es)

reactjavascript

Cuando iniciamos un desarrollo en React muchas veces nos preguntamos como es que se puede editar una variable y que se vea reflejaeda en la pantalla. La solución por defecto que se nos propone es utilizar el Hook useState que toma un valor base y por medio de una función actualiza el valor que necesitamos:

import React from "react";

const Component = () => {
const [count, setCount] = React.useState(0); // el valor por defecto de count será 0

return (
<div className="container">
<div>Contador : {count}</div>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
};
export default Component;

Sin embargo, useSate vuelve a renderizar el componente:

import React from "react";

const Component = () => {
console.log('Render component')
const [count, setCount] = React.useState(0);

return (
<div className="container">
<div>Contador : {count}</div>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
};

export default Component;

Imgur

Esto supone un problema dado que existen ocaciones donde solo queremos actualizar el valor y no perder la referencia de un render pasado.

const OtherComponent = ({ sec }: { sec: number }) => {

console.log("Render Other component - ",sec);
return (
<div className="other-component">
<h1>Other Component</h1>
<p>Seconds: {sec}</p>
</div>
);
};

const Component = () => {

const [count, setCount] = React.useState(0);
const [sec, setSec] = React.useState(0);
setInterval(() => {
setSec(sec + 1);
}, 1000);

console.log("Render component");
return (
<div className="container">
<OtherComponent sec={sec} />
<div>Contador : {count}</div>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
};
export default Component;

Ejemplo de useState: el componente se recarga por completo con cada clic en el botón

Cuando se vuelve a renderizar un componente sus animaciones vuelven a un estado inicial y los cambios locales de ese componente se reinician por lo que a la larga es un problema complejo de manejar.

Pero, ¿Cómo lo podemos solucionar?

React nos provee un Hook: useRef que mantiene en un objecto el valor actual. useRef() devuelve un objeto { current: undefined } y useRef(1) devuelve un objeto { current: 1 } por lo que podemos aprovecharnos para almacenar datos que no nos intersa que se muestren hasta que se actualice un componente:

import React, { useEffect } from "react";
import "./index.scss";

const OtherComponent = ({ sec }: { sec: any }) => {

useEffect(() => {
console.log("Other component - ",sec);
}, [sec]);
return (
<div className="other-component">
<h1>Other Component</h1>
<p>Seconds: {sec}</p>
</div>
);
};

const Component = () => {

const [count, setCount] = React.useState(0);
const sec = React.useRef(0);
useEffect(() => {
setInterval(() => {
sec.current++;
console.log('sec', sec.current);
}, 1000);
}, []);

console.log("Render component");
return (
<div className="container">
<OtherComponent sec={sec.current} />
<div>Contador : {count}</div>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
};
export default Component;

Ejemplo con useRef: el componente solo se recarga cuando se hace un useState

useRef es una buena herramienta para almacenar datos que perduran incluso despues de un re-render sin embargo los cambios que se hacen en el objecto current no fuerza un render por lo que es necesario que un useEffect o useState realice una actualización.

¿ Cuando utilizamos useState ?

¿ Cuando utilizamos useRef ?

En resumen:

Ambos conservan sus datos durante los ciclos de renderizado y las actualizaciones de la interfaz de usuario, pero solo el useStateHook con su función de actualización hace que se vuelvan a renderizar.