Elegir la estructura del estado
Estructurar bien el estado puede marcar la diferencia entre un componente que es agradable de modificar y depurar, y uno que es una fuente constante de errores. Aquí hay algunos consejos que deberías considerar al estructurar el estado.
Aprenderás
- Cuándo usar una sola variable de estado frente a múltiples
- Qué evitar al organizar el estado
- Cómo solucionar problemas comunes con la estructura del estado
Principios para estructurar el estado
Cuando escribes un componente que contiene algún estado, tendrás que tomar decisiones sobre cuántas variables de estado usar y cuál debería ser la forma de sus datos. Si bien es posible escribir programas correctos incluso con una estructura de estado subóptima, hay algunos principios que pueden guiarte para tomar mejores decisiones:
- Agrupa el estado relacionado.Si siempre actualizas dos o más variables de estado al mismo tiempo, considera fusionarlas en una sola variable de estado.
- Evita contradicciones en el estado.Cuando el estado está estructurado de manera que varias partes del estado pueden contradecirse y "estar en desacuerdo" entre sí, dejas espacio para errores. Intenta evitar esto.
- Evita el estado redundante.Si puedes calcular alguna información a partir de las props del componente o de sus variables de estado existentes durante el renderizado, no debes poner esa información en el estado de ese componente.
- Evita la duplicación en el estado.Cuando los mismos datos se duplican entre múltiples variables de estado, o dentro de objetos anidados, es difícil mantenerlos sincronizados. Reduce la duplicación cuando puedas.
- Evita el estado profundamente anidado.El estado con una jerarquía profunda no es muy conveniente de actualizar. Cuando sea posible, prefiere estructurar el estado de manera plana.
El objetivo detrás de estos principios eshacer que el estado sea fácil de actualizar sin introducir errores. Eliminar datos redundantes y duplicados del estado ayuda a garantizar que todas sus partes permanezcan sincronizadas. Esto es similar a cómo un ingeniero de bases de datos podría querer"normalizar" la estructura de la base de datospara reducir la posibilidad de errores. Parafraseando a Albert Einstein,"Haz que tu estado sea tan simple como pueda ser, pero no más simple".
Ahora veamos cómo se aplican estos principios en la práctica.
Agrupa el estado relacionado
A veces puedes tener dudas entre usar una sola variable de estado o múltiples.
¿Deberías hacer esto?
¿O esto?
Técnicamente, puedes usar cualquiera de estos enfoques. Perosi dos variables de estado siempre cambian juntas, puede ser una buena idea unificarlas en una sola variable de estado.Así no olvidarás mantenerlas siempre sincronizadas, como en este ejemplo donde mover el cursor actualiza ambas coordenadas del punto rojo:
Otro caso en el que agruparás datos en un objeto o un array es cuando no sabes cuántas piezas de estado necesitarás. Por ejemplo, es útil cuando tienes un formulario donde el usuario puede agregar campos personalizados.
Precaución
Si tu variable de estado es un objeto, recuerda queno puedes actualizar solo un campo en élsin copiar explícitamente los otros campos. Por ejemplo, no puedes hacersetPosition({ x: 100 })en el ejemplo anterior porque no tendría la propiedadyen absoluto. En su lugar, si quisieras establecer solox, harías setPosition({ ...position, x: 100 }), o las dividirías en dos variables de estado y haríassetX(100).
Evita contradicciones en el estado
Aquí hay un formulario de comentarios de hotel con las variables de estadoisSending y isSent:
Aunque este código funciona, deja la puerta abierta a estados "imposibles". Por ejemplo, si olvidas llamarsetIsSent y setIsSendingjuntos, puedes terminar en una situación donde tantoisSendingcomoisSentsontrueal mismo tiempo. Cuanto más complejo sea tu componente, más difícil será entender qué sucedió.
Dado queisSending y isSentnunca deberían sertrueal mismo tiempo, es mejor reemplazarlos con una única variable de estadostatusque puede tomar uno detresestados válidos:'typing'(inicial),'sending', y'sent':
Aún puedes declarar algunas constantes para mejorar la legibilidad:
Pero no son variables de estado, así que no necesitas preocuparte de que se desincronicen entre sí.
Evita el estado redundante
Si puedes calcular alguna información a partir de las props del componente o de sus variables de estado existentes durante el renderizado,no deberíasponer esa información en el estado de ese componente.
Por ejemplo, toma este formulario. Funciona, pero ¿puedes encontrar algún estado redundante en él?
Este formulario tiene tres variables de estado:firstName,lastName, yfullName. Sin embargo,fullNamees redundante.Siempre puedes calcularfullNamea partir defirstName y lastNamedurante el renderizado, así que elimínalo del estado.
Así es como puedes hacerlo:
Aquí,fullName no esuna variable de estado. En su lugar, se calcula durante el renderizado:
Como resultado, los manejadores de cambio no necesitan hacer nada especial para actualizarlo. Cuando llamas asetFirstName o setLastName, desencadenas un nuevo renderizado, y luego el siguientefullNamese calculará a partir de los datos frescos.
Evita la duplicación en el estado
Este componente de lista de menú te permite elegir un solo snack de viaje entre varios:
Actualmente, almacena el elemento seleccionado como un objeto en la variable de estadoselectedItem. Sin embargo, esto no es ideal:el contenido deselectedItemes el mismo objeto que uno de los elementos dentro de la listaitems.Esto significa que la información sobre el elemento en sí está duplicada en dos lugares.
¿Por qué es esto un problema? Hagamos que cada elemento sea editable:
Observa cómo si primero haces clic en "Choose" en un elemento yluegolo editas,la entrada se actualiza pero la etiqueta en la parte inferior no refleja las ediciones.Esto se debe a que tienes el estado duplicado y olvidaste actualizarselectedItem.
Aunque podrías actualizarselectedItemtambién, una solución más fácil es eliminar la duplicación. En este ejemplo, en lugar de un objetoselectedItem(que crea una duplicación con los objetos dentro deitems), mantienes elselectedIden el estado, yluegoobtienes elselectedItembuscando en el arrayitemsun elemento con ese ID:
El estado solía estar duplicado así:
items = [{ id: 0, title: 'pretzels'}, ...]selectedItem = {id: 0, title: 'pretzels'}
Pero después del cambio queda así:
items = [{ id: 0, title: 'pretzels'}, ...]selectedId = 0
¡La duplicación desapareció y solo mantienes el estado esencial!
Ahora, si editas el elementoseleccionado, el mensaje de abajo se actualizará inmediatamente. Esto se debe a quesetItemsdesencadena una nueva renderización, yitems.find(...)encontraría el elemento con el título actualizado. No necesitabas mantenerel elemento seleccionadoen el estado, porque solo elID seleccionadoes esencial. El resto se podía calcular durante la renderización.
Evita el estado profundamente anidado
Imagina un plan de viaje que consta de planetas, continentes y países. Podrías sentirte tentado a estructurar su estado usando objetos y arrays anidados, como en este ejemplo:
Ahora digamos que quieres agregar un botón para eliminar un lugar que ya visitaste. ¿Cómo lo harías?Actualizar el estado anidadoimplica hacer copias de objetos desde la parte que cambió hacia arriba. Eliminar un lugar profundamente anidado implicaría copiar toda su cadena de lugares padre. Ese tipo de código puede ser muy verboso.
Si el estado está demasiado anidado para actualizarlo fácilmente, considera hacerlo "plano".Aquí hay una forma en que puedes reestructurar estos datos. En lugar de una estructura en forma de árbol donde cadaplacetiene un array desus lugares hijos, puedes hacer que cada lugar contenga un array delos IDs de sus lugares hijos. Luego almacena un mapeo desde cada ID de lugar al lugar correspondiente.
Esta reestructuración de datos podría recordarte ver una tabla de base de datos:
Ahora que el estado está "plano" (también conocido como "normalizado"), actualizar elementos anidados se vuelve más fácil.
Para eliminar un lugar ahora, solo necesitas actualizar dos niveles de estado:
- La versión actualizada de su lugarpadredebe excluir el ID eliminado de su array
childIds. - La versión actualizada del objeto "tabla" raíz debe incluir la versión actualizada del lugar padre.
Aquí hay un ejemplo de cómo podrías hacerlo:
Puedes anidar el estado tanto como quieras, pero hacerlo "plano" puede resolver numerosos problemas. Facilita la actualización del estado y ayuda a garantizar que no haya duplicación en diferentes partes de un objeto anidado.
A veces, también puedes reducir el anidamiento del estado moviendo parte del estado anidado a los componentes hijos. Esto funciona bien para el estado de la interfaz de usuario efímero que no necesita almacenarse, como si un elemento está siendo señalado con el cursor.
Recapitulación
- Si dos variables de estado siempre se actualizan juntas, considera fusionarlas en una.
- Elige tus variables de estado con cuidado para evitar crear estados "imposibles".
- Estructura tu estado de manera que reduzca las posibilidades de cometer un error al actualizarlo.
- Evita el estado redundante y duplicado para que no tengas que mantenerlo sincronizado.
- No pongas las propsenel estado a menos que específicamente quieras evitar actualizaciones.
- Para patrones de interfaz de usuario como la selección, mantén el ID o el índice en el estado en lugar del objeto en sí.
- Si actualizar un estado profundamente anidado es complicado, intenta aplanarlo.
Try out some challenges
Challenge 1 of 4:Fix a component that’s not updating #
This Clock component receives two props: color and time. When you select a different color in the select box, the Clock component receives a different color prop from its parent component. However, for some reason, the displayed color doesn’t update. Why? Fix the problem.
