v19.2Latest

Preservar y restablecer el estado

El estado está aislado entre componentes. React realiza un seguimiento de qué estado pertenece a qué componente según su ubicación en el árbol de la interfaz de usuario. Puedes controlar cuándo preservar el estado y cuándo restablecerlo entre re-renderizados.

Aprenderás
  • Cuándo React decide preservar o restablecer el estado
  • Cómo forzar a React a restablecer el estado de un componente
  • Cómo las claves y los tipos afectan si el estado se preserva

El estado está vinculado a una posición en el árbol de renderizado

React construyeárboles de renderizadopara la estructura de componentes en tu interfaz de usuario.

Cuando le das estado a un componente, podrías pensar que el estado "vive" dentro del componente. Pero el estado en realidad lo mantiene React. React asocia cada pieza de estado que mantiene con el componente correcto según dónde se encuentre ese componente en el árbol de renderizado.

Aquí, solo hay una etiqueta JSX<Counter />, pero se renderiza en dos posiciones diferentes:

Así es como se ven como un árbol:

Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. Cada uno de los hijos está etiquetado como 'Counter' y ambos contienen una burbuja de estado etiquetada como 'count' con valor 0.Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. Cada uno de los hijos está etiquetado como 'Counter' y ambos contienen una burbuja de estado etiquetada como 'count' con valor 0.

Árbol de React

Estos son dos contadores separados porque cada uno se renderiza en su propia posición en el árbol.Normalmente no tienes que pensar en estas posiciones para usar React, pero puede ser útil entender cómo funciona.

En React, cada componente en la pantalla tiene un estado completamente aislado. Por ejemplo, si renderizas dos componentesCounteruno al lado del otro, cada uno obtendrá sus propios estadosscore y hover, independientes.

Prueba a hacer clic en ambos contadores y observa que no se afectan entre sí:

Como puedes ver, cuando se actualiza un contador, solo se actualiza el estado de ese componente:

Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. El hijo izquierdo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada como 'count' con valor 0. El hijo derecho está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada como 'count' con valor 1. La burbuja de estado del hijo derecho está resaltada en amarillo para indicar que su valor se ha actualizado.Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. El hijo izquierdo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada como 'count' con valor 0. El hijo derecho está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada como 'count' con valor 1. La burbuja de estado del hijo derecho está resaltada en amarillo para indicar que su valor se ha actualizado.

Actualizando el estado

React mantendrá el estado mientras renderices el mismo componente en la misma posición del árbol. Para ver esto, incrementa ambos contadores, luego elimina el segundo componente desmarcando la casilla de verificación “Render the second counter”, y luego agrégalo de nuevo marcándola otra vez:

Observa cómo en el momento en que dejas de renderizar el segundo contador, su estado desaparece por completo. Esto se debe a que cuando React elimina un componente, destruye su estado.

Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. El hijo izquierdo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada como 'count' con valor 0. El hijo derecho falta, y en su lugar hay una imagen amarilla de 'poof', resaltando que el componente se está eliminando del árbol.Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. El hijo izquierdo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0. El hijo derecho falta, y en su lugar hay una imagen amarilla de 'poof', resaltando que el componente se eliminó del árbol.

Eliminar un componente

Cuando marcas “Render the second counter”, un segundoCountery su estado se inicializan desde cero (score = 0) y se añaden al DOM.

Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. El hijo izquierdo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0. El hijo derecho está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0. Todo el nodo hijo derecho está resaltado en amarillo, indicando que se acaba de añadir al árbol.Diagrama de un árbol de componentes de React. El nodo raíz está etiquetado como 'div' y tiene dos hijos. El hijo izquierdo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0. El hijo derecho está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0. Todo el nodo hijo derecho está resaltado en amarillo, indicando que se acaba de añadir al árbol.

Añadir un componente

React preserva el estado de un componente mientras se renderiza en su posición en el árbol de la interfaz de usuario.Si se elimina, o si un componente diferente se renderiza en la misma posición, React descarta su estado.

El mismo componente en la misma posición preserva el estado

En este ejemplo, hay dos etiquetas<Counter />diferentes:

Cuando marcas o desmarcas la casilla, el estado del contador no se reinicia. Ya sea queisFancyseatrue o false, siempre tienes un<Counter />como primer hijo deldivdevuelto por el componente raízApp:

Diagrama con dos secciones separadas por una flecha que indica una transición entre ellas. Cada sección contiene un diseño de componentes con un padre etiquetado 'App' que contiene una burbuja de estado etiquetada isFancy. Este componente tiene un hijo etiquetado 'div', que conduce a una burbuja de props que contiene isFancy (resaltada en púrpura) que se pasa al único hijo. El último hijo está etiquetado 'Counter' y contiene una burbuja de estado con la etiqueta 'count' y el valor 3 en ambos diagramas. En la sección izquierda del diagrama, nada está resaltado y el valor del estado padre isFancy es false. En la sección derecha del diagrama, el valor del estado padre isFancy ha cambiado a true y está resaltado en amarillo, al igual que la burbuja de props debajo, que también ha cambiado su valor isFancy a true.Diagrama con dos secciones separadas por una flecha que indica una transición entre ellas. Cada sección contiene un diseño de componentes con un padre etiquetado 'App' que contiene una burbuja de estado etiquetada isFancy. Este componente tiene un hijo etiquetado 'div', que conduce a una burbuja de props que contiene isFancy (resaltada en púrpura) que se pasa al único hijo. El último hijo está etiquetado 'Counter' y contiene una burbuja de estado con la etiqueta 'count' y el valor 3 en ambos diagramas. En la sección izquierda del diagrama, nada está resaltado y el valor del estado padre isFancy es false. En la sección derecha del diagrama, el valor del estado padre isFancy ha cambiado a true y está resaltado en amarillo, al igual que la burbuja de props debajo, que también ha cambiado su valor isFancy a true.

Actualizar el estado deAppno reinicia elCounterporqueCounterpermanece en la misma posición

Es el mismo componente en la misma posición, así que, desde la perspectiva de React, es el mismo contador.

Pitfall

Recuerda que¡lo que importa para React es la posición en el árbol de la interfaz de usuario, no en el marcado JSX!Este componente tiene dos cláusulasreturncon diferentes etiquetas JSX<Counter />dentro y fuera delif:

Podrías esperar que el estado se reinicie cuando marcas la casilla, ¡pero no lo hace! Esto se debe a queambas etiquetas<Counter />se renderizan en la misma posición.React no sabe dónde colocas las condiciones en tu función. Todo lo que "ve" es el árbol que devuelves.

En ambos casos, el componenteAppdevuelve un<div>con<Counter />como primer hijo. Para React, estos dos contadores tienen la misma "dirección": el primer hijo del primer hijo de la raíz. Así es como React los empareja entre el renderizado anterior y el siguiente, independientemente de cómo estructures tu lógica.

Componentes diferentes en la misma posición reinician el estado

En este ejemplo, marcar la casilla reemplazará<Counter>con un<p>:

Aquí, cambias entrediferentestipos de componentes en la misma posición. Inicialmente, el primer hijo del<div>contenía unCounter. Pero cuando lo cambiaste por unp, React eliminó elCounterdel árbol de la interfaz de usuario y destruyó su estado.

Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'div' con un único hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 3. La sección del medio tiene el mismo padre 'div', pero el componente hijo ahora ha sido eliminado, indicado por una imagen amarilla de 'prueba'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'p', resaltado en amarillo.Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'div' con un único hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 3. La sección del medio tiene el mismo padre 'div', pero el componente hijo ahora ha sido eliminado, indicado por una imagen amarilla de 'prueba'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'p', resaltado en amarillo.

CuandoCountercambia ap, elCounterse elimina y se añade elp.

Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'p'. La sección del medio tiene el mismo padre 'div', pero el componente hijo ahora ha sido eliminado, indicado por una imagen amarilla de 'prueba'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0, resaltado en amarillo.Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'p'. La sección del medio tiene el mismo padre 'div', pero el componente hijo ahora ha sido eliminado, indicado por una imagen amarilla de 'prueba'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0, resaltado en amarillo.

Al cambiar de vuelta, elpse elimina y se añade elCounter.

Además,cuando renderizas un componente diferente en la misma posición, se reinicia el estado de todo su subárbol.Para ver cómo funciona esto, incrementa el contador y luego marca la casilla:

El estado del contador se reinicia cuando haces clic en la casilla. Aunque renderizas unCounter, el primer hijo deldivcambia de unsectiona undiv. Cuando el hijosectionfue eliminado del DOM, todo el árbol debajo de él (incluyendo elCountery su estado) también fue destruido.

Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'div' con un único hijo etiquetado 'section', que tiene un único hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 3. La sección del medio tiene el mismo padre 'div', pero los componentes hijos ahora han sido eliminados, indicado por una imagen amarilla de 'prueba'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'div', resaltado en amarillo, también con un nuevo hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0, todo resaltado en amarillo.Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'div' con un único hijo etiquetado 'section', que tiene un único hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 3. La sección del medio tiene el mismo padre 'div', pero los componentes hijos ahora han sido eliminados, indicado por una imagen amarilla de 'prueba'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'div', resaltado en amarillo, también con un nuevo hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0, todo resaltado en amarillo.

Cuandosectioncambia adiv, elsectionse elimina y se añade el nuevodiv.

Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'div' con un único hijo etiquetado 'div', que tiene un único hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0. La sección del medio tiene el mismo padre 'div', pero los componentes hijos ahora han sido eliminados, indicado por una imagen amarilla 'proof'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'section', resaltado en amarillo, también con un nuevo hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0, todo resaltado en amarillo.Diagrama con tres secciones, con una flecha que transiciona entre cada sección. La primera sección contiene un componente React etiquetado 'div' con un único hijo etiquetado 'div', que tiene un único hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0. La sección del medio tiene el mismo padre 'div', pero los componentes hijos ahora han sido eliminados, indicado por una imagen amarilla 'proof'. La tercera sección tiene el mismo padre 'div' nuevamente, ahora con un nuevo hijo etiquetado 'section', resaltado en amarillo, también con un nuevo hijo etiquetado 'Counter' que contiene una burbuja de estado etiquetada 'count' con valor 0, todo resaltado en amarillo.

Al cambiar de vuelta, eldivse elimina y se añade el nuevosection

Como regla general,si quieres preservar el estado entre re-renderizados, la estructura de tu árbol necesita "coincidir"de un renderizado a otro. Si la estructura es diferente, el estado se destruye porque React destruye el estado cuando elimina un componente del árbol.

Pitfall

Por eso no debes anidar definiciones de funciones de componentes.

Aquí, la función del componenteMyTextField se define dentroMyComponent:

Cada vez que haces clic en el botón, ¡el estado del input desaparece! Esto se debe a que se crea una funcióndiferenteMyTextFieldpara cada renderizado deMyComponent. Estás renderizando un componentediferenteen la misma posición, por lo que React restablece todo el estado por debajo. Esto conduce a errores y problemas de rendimiento. Para evitar este problema,declara siempre las funciones de componentes en el nivel superior y no anides sus definiciones.

Restablecer el estado en la misma posición

Por defecto, React preserva el estado de un componente mientras permanece en la misma posición. Por lo general, esto es exactamente lo que quieres, por lo que tiene sentido como comportamiento predeterminado. Pero a veces, puedes querer restablecer el estado de un componente. Considera esta aplicación que permite a dos jugadores llevar un registro de sus puntuaciones durante cada turno:

Actualmente, cuando cambias de jugador, la puntuación se preserva. Los dosCounters aparecen en la misma posición, por lo que React los ve comoel mismoCountercuyo proppersonha cambiado.

Pero conceptualmente, en esta aplicación deberían ser dos contadores separados. Pueden aparecer en el mismo lugar en la interfaz de usuario, pero uno es un contador para Taylor y otro es un contador para Sarah.

Hay dos formas de restablecer el estado al cambiar entre ellos:

  1. Renderizar componentes en diferentes posiciones
  2. Dar a cada componente una identidad explícita conkey

Opción 1: Renderizar un componente en diferentes posiciones

Si quieres que estos dosCounters sean independientes, puedes renderizarlos en dos posiciones diferentes:

  • Inicialmente,isPlayerAestrue. Así que la primera posición contiene el estado deCounter, y la segunda está vacía.
  • Cuando haces clic en el botón "Siguiente jugador", la primera posición se vacía pero la segunda ahora contiene unCounter.
Diagrama con un árbol de componentes React. El padre está etiquetado como 'Scoreboard' con una burbuja de estado etiquetada isPlayerA con valor 'true'. El único hijo, dispuesto a la izquierda, está etiquetado como Counter con una burbuja de estado etiquetada 'count' y valor 0. Todo el hijo izquierdo está resaltado en amarillo, indicando que fue añadido.Diagrama con un árbol de componentes React. El padre está etiquetado como 'Scoreboard' con una burbuja de estado etiquetada isPlayerA con valor 'true'. El único hijo, dispuesto a la izquierda, está etiquetado como Counter con una burbuja de estado etiquetada 'count' y valor 0. Todo el hijo izquierdo está resaltado en amarillo, indicando que fue añadido.

Estado inicial

Diagrama con un árbol de componentes React. El padre está etiquetado como 'Scoreboard' con una burbuja de estado etiquetada isPlayerA con valor 'false'. La burbuja de estado está resaltada en amarillo, indicando que ha cambiado. El hijo izquierdo es reemplazado por una imagen amarilla de 'poof' indicando que ha sido eliminado y hay un nuevo hijo a la derecha, resaltado en amarillo indicando que fue añadido. El nuevo hijo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0.Diagrama con un árbol de componentes React. El padre está etiquetado como 'Scoreboard' con una burbuja de estado etiquetada isPlayerA con valor 'false'. La burbuja de estado está resaltada en amarillo, indicando que ha cambiado. El hijo izquierdo es reemplazado por una imagen amarilla de 'poof' indicando que ha sido eliminado y hay un nuevo hijo a la derecha, resaltado en amarillo indicando que fue añadido. El nuevo hijo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0.

Haciendo clic en “siguiente”

Diagrama con un árbol de componentes React. El padre está etiquetado como 'Scoreboard' con una burbuja de estado etiquetada isPlayerA con valor 'true'. La burbuja de estado está resaltada en amarillo, indicando que ha cambiado. Hay un nuevo hijo a la izquierda, resaltado en amarillo indicando que fue añadido. El nuevo hijo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0. El hijo derecho es reemplazado por una imagen amarilla de 'poof' indicando que ha sido eliminado.Diagrama con un árbol de componentes React. El padre está etiquetado como 'Scoreboard' con una burbuja de estado etiquetada isPlayerA con valor 'true'. La burbuja de estado está resaltada en amarillo, indicando que ha cambiado. Hay un nuevo hijo a la izquierda, resaltado en amarillo indicando que fue añadido. El nuevo hijo está etiquetado como 'Counter' y contiene una burbuja de estado etiquetada 'count' con valor 0. El hijo derecho es reemplazado por una imagen amarilla de 'poof' indicando que ha sido eliminado.

Haciendo clic en “siguiente” de nuevo

El estado de cadaCounterse destruye cada vez que se elimina del DOM. Por eso se reinician cada vez que haces clic en el botón.

Esta solución es conveniente cuando solo tienes unos pocos componentes independientes renderizados en el mismo lugar. En este ejemplo, solo tienes dos, así que no es un problema renderizar ambos por separado en el JSX.

Opción 2: Reiniciar el estado con una clave

También hay otra forma, más genérica, de reiniciar el estado de un componente.

Es posible que hayas visto laskeys alrenderizar listas.¡Las claves no son solo para listas! Puedes usar claves para que React distinga entre cualquier componente. Por defecto, React usa el orden dentro del padre ("primer contador", "segundo contador") para discernir entre componentes. Pero las claves te permiten decirle a React que esto no es solo unprimercontador, o unsegundocontador, sino un contador específico, por ejemplo, el contador deTaylor. De esta manera, React sabrá el contador deTaylordondequiera que aparezca en el árbol.

En este ejemplo, los dos<Counter />no comparten estado aunque aparezcan en el mismo lugar en el JSX:

Cambiar entre Taylor y Sarah no preserva el estado. Esto se debe a queles diste diferenteskeys:

Especificar unakeyle dice a React que use lakeymisma como parte de la posición, en lugar de su orden dentro del padre. Por eso, aunque los renderices en el mismo lugar en el JSX, React los ve como dos contadores diferentes, y por lo tanto nunca compartirán estado. Cada vez que un contador aparece en pantalla, su estado se crea. Cada vez que se elimina, su estado se destruye. Alternar entre ellos reinicia su estado una y otra vez.

Nota

Recuerda que las claves no son globalmente únicas. Solo especifican la posicióndentro del padre.

Reiniciar un formulario con una clave

Reiniciar el estado con una clave es particularmente útil cuando se trabaja con formularios.

En esta aplicación de chat, el componente<Chat>contiene el estado de entrada de texto:

Intenta escribir algo en el campo de entrada y luego presiona “Alice” o “Bob” para elegir un destinatario diferente. Notarás que el estado de la entrada se conserva porque el<Chat>se renderiza en la misma posición en el árbol.

¡En muchas aplicaciones, este puede ser el comportamiento deseado, pero no en una aplicación de chat!No quieres permitir que el usuario envíe un mensaje que ya escribió a la persona equivocada debido a un clic accidental. Para solucionarlo, agrega unakey:

Esto asegura que cuando seleccionas un destinatario diferente, el componenteChatse recreará desde cero, incluyendo cualquier estado en el árbol debajo de él. React también recreará los elementos DOM en lugar de reutilizarlos.

Ahora cambiar el destinatario siempre borra el campo de texto:

Deep Dive
Conservar el estado para componentes eliminados

Recapitulación

  • React conserva el estado mientras el mismo componente se renderiza en la misma posición.
  • El estado no se guarda en las etiquetas JSX. Está asociado con la posición en el árbol donde colocas ese JSX.
  • Puedes forzar a un subárbol a restablecer su estado dándole una key diferente.
  • No anides definiciones de componentes, o restablecerás el estado accidentalmente.

Try out some challenges

Challenge 1 of 5:Fix disappearing input text #

This example shows a message when you press the button. However, pressing the button also accidentally resets the input. Why does this happen? Fix it so that pressing the button does not reset the input text.