Original article: How to become a pro with React setState() in 10 minutes
Este artículo está dirigido a personas que ya han tenido su primer acercamiento a React, y que, como principiantes, tienen dudas sobre cómo funciona setState
y cómo usarlo correctamente. También debería ayudar a los desarrolladores de nivel medio a superior a usar formas más limpias y abstractas de establecer el estado, y hacer que las funciones de orden superior manejen y abstraigan el estado.
¡Solo lee y diviértete!
¡Así que toma una taza de café y sigue leyendo!
Conceptos básicos de setState( )
Los componentes de React le permiten dividir la interfaz de usuario (IU) en piezas independientes y reutilizables, para que pueda pensar en cada pieza de forma aislada.
Conceptualmente, los componentes son como funciones de JavaScript. Aceptan entradas arbitrarias (llamadas "propiedades" o "props") y devuelven elementos React que describen lo que debería aparecer en la pantalla.
Si necesita darle al usuario la oportunidad de ingresar algo o de alguna manera cambiar las variables que el componente recibe como propiedades, necesitará setState
.
Ya sea que declare un Componente como una función o una clase, nunca debe modificar sus propios accesorios.
Todos los componentes de React deben actuar como funciones puras con respecto a sus propiedades. Esto significa funciones que nunca intentan cambiar sus entradas y siempre devuelven el mismo resultado para las mismas entradas.
Por supuesto, las interfaces de usuario de las aplicaciones son dinámicas y cambian con el tiempo. Por eso se creó state
.
State
permite que los componentes de React cambien su salida con el tiempo en respuesta a las acciones del usuario, las respuestas de la red y cualquier otra cosa, sin violar esta regla.
Los Componentes definidos como clases tienen algunas características adicionales. El estado local es una función disponible solo para los Componentes de clase.
setState
es el método API proporcionado con la librería para que el usuario pueda definir y manipular el estado a lo largo del tiempo.
Tres reglas generales al usar setState( )
No modifiques el estado directamente
Las actualizaciones de estado pueden ser asincrónicas
React puede agrupar varias llamadas setState()
en una sola actualización para mejorar el rendimiento.
Debido a que this.props
y this.state
pueden actualizarse de forma asíncrona, no debe confiar en sus valores para calcular el siguiente estado.
Siempre debe hacer este tipo de manipulación con un enfoque funcional, proporcionando el state
y los props
y devolviendo el nuevo state
basado en el anterior.
Las actualizaciones de estado se fusionan
Cuando llamas a setState()
, React fusiona el objeto que proporcionas en el resto del state
actual.
En el siguiente ejemplo, estamos actualizando la variable dogNeedsVaccination
independientemente de las otras variables de state
.
La fusión es superficial, por lo que this.setState({ dogNeedsVaccination: true })
deja intactas las otras variables, reemplazando solo el valor de dogNeedsVaccination
.
Respeta el flujo de datos y evita el estado al máximo
¡Los datos fluyen hacia abajo! Ni los componentes principales ni los secundarios pueden saber si un determinado componente tiene estado o no, y no debería importarles si se define como una función o una clase.
Es por eso que el state
a menudo se llama local o encapsulado. No es accesible a ningún componente que no sea el que lo posee y lo configura.
Cuando estableces una propiedad de estado y lo usas en tu componente, estás interrumpiendo el flujo de las propiedades de renderizado. Si por alguna razón la propiead pasad a tu componente cambia en el componente padre, ¿el hijo no se volverá a renderizar automáticamente por arte de magia?
Veamos un ejemplo:
Aquí tienes un Componente Home
que está generando un número mágico cada 1000ms y poniéndolo en su propio state
.
Después de eso, representa el número e invoca a tres Componentes Child
(Hermanos) que recibirán el número mágico con el objetivo de mostrarlo usando tres enfoques diferentes:
Primer enfoque
El Componente ChildOfHome
está respetando el flujo de cascada de props de React y, considerando que el objetivo es solo mostrar el número mágico, está renderizando los props
recibidos directamente.
Segundo enfoque
El Componente ChildOfHomeBrother
recibe las props
de su padre y, invocando componentDidMount
, establece el número mágico en el state
. Luego representa el state.magicNumber
.
Este ejemplo no funcion porque render()
no sabe que una prop
ha cambiado, por lo que no activa la nueva representación del componente. Como el componente y no se vuelve a representar, no se invoca el componentDidMount
y la pantalla no se actualiza.
Tercer enfoque
Por lo general, cuando intentamos que funcione con el segundo enfoque, pensamos que falta algo. ¡En lugar de dar un paso atrás, seguimos agregando cosas al código para que funcione!
Entonces, en este tercer enfoque, agregamos componentDidUpdate
para verificar si hay un cambio en props
para activar la nueva representación del componente. Esto es innecesario y nos lleva a un código sucio. También trae consigo costos de rendimiento que se multiplicarán por la cantidad de veces que hagamos esto en una aplicación grande donde tenemos muchos componentes encadenados y efectos secundarios.
Esto es incorrecto a menos que necesite permitir que el usuario cambie el valor de prop recibido.
Si no necesita cambiar el valor de prop, siempre trate de mantener las cosas funcionando de acuerdo con el Flujo de React (Primer Enfoque).
Puede consultar una página web en funcionamiento con este ejamplo que he preparado para ti en Glitch. ¿Echa un vistazo y diviértete?
Consulta también el código en Home.js
y HomeCodeCleaned.js
(sin las cosas de HTML) en mi repositorio sobre este artículo.
Cómo establecer el estado con setState
Entonces, llegados a este punto, ¡creo que es hora de ensuciarse las manos!
¡Juguemos un poco con setState
y mejoremos eso! Solo sigue y toma otra taza de café!
Vamos a crear un pequeño formulario para actualizar los datos del usuario:
Aquí está el código para el ejemplo anterior:
Estamos configurando el state
como un objeto y no hay problema porque nuestro estado actual no depende de nuestro último estado.
¿Qué pasa si creamos un campo de formulario más para introducir y mostrar el Apellido (Last Name)?
¡Bien! Hemos abstraído el método handleFormChange
para poder manejar todos los campos de entrada y setState
.
¿Qué pasa si agregamos un botón para marcar los datos como válidos o no válidos y un contador para saber cuántos cambios hemos hecho en el state
?
¡Sí! ¡Lo estamos petando! ¡Hemos abstraído muchas cosas!
Hmmm... Digamos que no quiero una casilla de verificación para controlar la variable, isValid
sino un simple botón de alternancia.
También separemos el controlador de contador de este método. Funciona bien, pero en situaciones más complejas en las que React necesita cambios por lotes/grupos, no es una buena política confiar en la variable this.state.counter
para agregar uno más. Este valor puede cambiar sin que te des cuenta.
Estamos usando una copia superficial del mismo en el instante en que se invoca la operación, y en ese momento determinado no sabes si su valor es el que esperabas o no.
¡Vamos a ponernos un poco funcionales!
Vale – Hemos perdido la abstracción porque hemos separado los controladores, ¡pero es por una buena razón!
Así que en este momento mantenemos handleFormChange
pasando un objeto al método API setState
. Pero los métodos handleCounter
y handleIsValid
ahora son funcionales y comienzan tomando el estado actual y luego, dependiendo de ese estado, cambiándolo al siguiente.
Esta es la forma correcta de cambiar el estado de las variables que dependen del estado anterior.
¿Qué pasa si queremos cambiar el estado de console.log()
de los formularios de entrada firstName
y lastName
cada vez que ocurre un cambio? ¡Hagamos un intento!
¡Muy bien! Cada vez que se produce handleFormChange
(lo que significa que se ha pulsado una nueva tecla), se invoca el método logFields()
y se registra el estado actual en la consola.
Comprobemos la consola del navegador:
¡Espera! ¿Qué ha pasado aquí, amigo? ¡El registro de la consola muestra un cambio antes del cambio en el formulario actual! ¿Por qué está pasando esto?
¡¡setState es asíncrono!!
Ya lo sabíamos, ¡pero ahora lo estamos viendo con nuestros propios ojos! ¿Lo que está pasando allí? Echemos un vistazo a los métodos handleFormChange
y logFields
anteriores.
Entonces, el método handleFormChange
recibe el nombre y el valor del evento, luego hace un setState
de estos datos. Luego llama a handleCounter
para actualizar la información del contador y, al final, invoca el método logFields.
El método logFields
toma el estado actual currentState
y devuelve 'Eduard' en lugar de 'Eduardo'.
La cosa es: setState
es asíncrono y no actúa en el momento. React está haciendo su trabajo y ejecuta primero el método logFields
, dejando setState
para el siguiente ciclo de eventos.
Pero, ¿cómo podemos evitar este tipo de situaciones?
Bien, la API setState
tiene una callback
(devolución de llamada) para evitar esta situación:
Si queremos que logFields()
tenga en cuenta los cambios recientes que hemos realizado en el estado, debemos invocarlo dentro de la devolución de llamada, así:
Bien, ¡ahora está funcionando!
Le decimos a React: “¡Oye, React! Ten cuidado de que cuando invoques el método logFields
, quiero que tenga el estado ya actualizado, ¿de acuerdo? ¡Confío en ti!"
React dice: “¡Está bien, Edo! ¡Voy a encargarme de todo este montón de cosas que suelo hacer en el patio trasero con setState
y solo cuando termine con eso invocaré logField()
! Tranquilo colega! Relájate!”
Y, de hecho, ¡funcionó!
¡Bien todos! En este momento hemos manejado los principales escollos de setState
.
¿Tienes el coraje de ir más allá? Toma una taza de café y pongámonos realmente chulos.
Poniéndose elegantes con setState( )
Ahora que tenemos los métodos handleCounter
y handleIsValid
, y setState()
expresado con funciones, ¡podemos componer la actualización de estado con otras funciones! ¡Me gusta la composición! ¡Vamos a divertirnos un poco!
Podemos llevar la lógica dentro de setState
a una función fuera del componente de clase. Llamémoslo toggleIsValid
. ☝️
Ahora esta función puede vivir fuera del componente de clase, en cualquier lugar de su aplicación.
¿Qué pasa si usamos una función de orden superior?
Guau! Ahora ya no estamos invocando la función toggleIsValid
. Invocamos una función abstracta de orden superior llamada toggleKey
y le pasamos una clave (una cadena en este caso).
¿Cómo necesitamos cambiar la función toggleIsValid
ahora?
¡¿Qué?! Ahora tenemos una función llamada toggleKey
que recibe una clave y devuelve una nueva función que cambia de estado según la clave suministrada.
Esta toggleKey
puede estar en una biblioteca o en un archivo auxiliar. Se puede invocar en muchos contextos diferentes para cambiar el estado de lo que quieras a su opuesto.
¡Genial!
Hagamos lo mismo con el controlador del contador de incrementos:
¡Sí! ¡Funciona! Que guay! Vamos a volvernos locos ahora...
Disparando a la luna y regresando
¿Qué sucede si creamos una función makeUpdater
genérica que recibe la función de transformación que desea aplicar, toma la clave y devuelve la función de estado que administra el estado con la función de transformación y la clave? ¿Un poco confundido? ¡Vamos!
Ok, eso es suficiente... Nos paramos aquí
Puedes consultar todo el código que hemos hecho en este repositorio de GitHub.
Por último, pero no menos importante
No olvides evitar al máximo el uso del estado y respetar la cascada de propiedades de renderizado de React.
No olvides que setState
es asíncrono.
No olvides que setState
puede tomar un objeto o una función
No olvides que debes pasar una función cuando tu próximo estado dependa de su estado anterior.
¡Muchas gracias!