Agregar interactividad
Algunas cosas en la pantalla se actualizan en respuesta a la entrada del usuario. Por ejemplo, hacer clic en una galería de imágenes cambia la imagen activa. En React, los datos que cambian con el tiempo se denominan estado. Puedes agregar estado a cualquier componente y actualizarlo según sea necesario. En este capítulo, aprenderás a escribir componentes que controlen interacciones, actualicen tu estado y muestren resultados diferentes a lo largo del tiempo.
En este capítulo
- Cómo controlar eventos iniciados por el usuario
- Cómo hacer que los componentes «recuerden» información con estado
- Cómo React actualiza la interfaz de usuario en dos fases
- Por qué el estado no se actualiza justo después de cambiarlo
- Cómo poner en cola varias actualizaciones de estado
- Cómo actualizar un objeto en el estado
- Cómo actualizar un array en el estado
Responder a eventos
React te permite agregar controladores de eventos a tu JSX. Los controladores de eventos son tus propias funciones que se activarán en respuesta a las interacciones del usuario, como hacer clic, pasar el mouse, enfocarse en las entradas de un formulario, etc.
Los componentes integrados como <button>
solo admiten eventos de navegador integrados como onClick
. Sin embargo, también puedes crear tus propios componentes y darle a sus props de controladores de eventos los nombres específicos de la aplicación que desees.
export default function App() { return ( <Toolbar onPlayMovie={() => alert('¡Reproduciendo!')} onUploadImage={() => alert('¡Cargando!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Reproducir película </Button> <Button onClick={onUploadImage}> Cargar imagen </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
¿Listo para aprender este tema?
Lee Responder a eventos para aprender cómo agregar controladores de eventos.
Lee másEl estado: la memoria de un componente
Los componentes a menudo necesitan cambiar lo que aparece en la pantalla como resultado de una interacción. Escribir en el formulario debería actualizar el campo de entrada, hacer clic en «siguiente» en un carrusel de imágenes debería cambiar la imagen que se muestra, hacer clic en «comprar» pone un producto en el carrito de compras. Los componentes necesitan «recordar» cosas: el valor de entrada actual, la imagen actual, el carrito de compras. En React, este tipo de memoria específica del componente se llama estado.
Puedes agregar estado a un componente con un useState
Hook. Los Hooks son funciones especiales que permiten que tus componentes usen funciones de React (el estado es una de esas funciones). El Hook useState
te permite declarar una variable de estado. Toma el estado inicial y devuelve un par de valores: el estado actual y una función de establecimiento de estado que te permite actualizarlo.
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
Así es como una galería de imágenes usa y actualiza el estado al hacer clic:
import { useState } from 'react'; import { sculptureList } from './data.js'; export default function Gallery() { const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); const hasNext = index < sculptureList.length - 1; function handleNextClick() { if (hasNext) { setIndex(index + 1); } else { setIndex(0); } } function handleMoreClick() { setShowMore(!showMore); } let sculpture = sculptureList[index]; return ( <> <button onClick={handleNextClick}> Siguiente </button> <h2> <i>{sculpture.name} </i> por {sculpture.artist} </h2> <h3> ({index + 1} de {sculptureList.length}) </h3> <button onClick={handleMoreClick}> {showMore ? 'Ocultar' : 'Mostrar'} detalles </button> {showMore && <p>{sculpture.description}</p>} <img src={sculpture.url} alt={sculpture.alt} /> </> ); }
¿Listo para aprender este tema?
Lee El estado: la memoria de un componente para aprender a recordar un valor y actualizarlo en la interacción.
Lee másRenderizado y confirmación
Antes de que tus componentes se muestren en la pantalla, deben ser renderizados por React. Comprender los pasos de este proceso te ayudará a pensar en cómo se ejecuta tu código y explicar su comportamiento.
Imagina que tus componentes son cocineros en la cocina, montando sabrosos platos a partir de los ingredientes. En este escenario, React es el camarero que hace las peticiones de los clientes y les trae sus pedidos. Este proceso de solicitud y servicio de UI tiene tres pasos:
- Desencadenamiento de un renderizado (entrega del pedido del cliente a la cocina)
- Renderizado del componente (preparación del pedido en la cocina)
- Confirmación con el DOM (poner el pedido sobre la mesa)
Ilustrado por Rachel Lee Nabors
¿Listo para aprender este tema?
Lee Renderizado y confirmación para conocer el ciclo de vida de una actualización de la interfaz de usuario.
Lee másEl estado como una instantánea
A diferencia de las variables regulares de JavaScript, el estado de React se comporta más como una instantánea. Establecerlo no cambia la variable de estado que ya tienes, sino que activa una nuevo renderizado. ¡Esto puede ser sorprendente al principio!
console.log(count); // 0
setCount(count + 1); // Solicitar un nuevo renderizado con 1.
console.log(count); // ¡Todavía 0!
Este comportamiento te ayuda a evitar errores sutiles. Aquí hay una pequeña aplicación de chat. Intenta adivinar qué sucede si presionas «Enviar» primero y luego cambias el destinatario a Bob. ¿El nombre de quién aparecerá en la alerta
cinco segundos después?
import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('Alice'); const [message, setMessage] = useState('Hola'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`Le dijiste ${message} a ${to}`); }, 5000); } return ( <form onSubmit={handleSubmit}> <label> Para:{' '} <select value={to} onChange={e => setTo(e.target.value)}> <option value="Alice">Alice</option> <option value="Bob">Bob</option> </select> </label> <textarea placeholder="Mensaje" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">Enviar</button> </form> ); }
¿Listo para aprender este tema?
Lee El estado como una instantánea para saber por qué el estado aparece «fijo» y sin cambios dentro de los controladores de eventos.
Lee másPoner en cola una serie de actualizaciones de estado
Este componente tiene errores: hacer clic en «+3» incrementa la puntuación solo una vez.
import { useState } from 'react'; export default function Counter() { const [score, setScore] = useState(0); function increment() { setScore(score + 1); } return ( <> <button onClick={() => increment()}>+1</button> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <h1>Puntaje: {score}</h1> </> ) }
El estado como una instantánea explica por qué sucede esto. Al establecer el estado se solicita un nuevo rerenderizado, pero no lo cambia en el código que ya se está ejecutando. Entonces score
sigue siendo 0
justo después de llamar a setScore(score + 1)
.
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
Puedes solucionar esto pasando una función de actualización al configurar el estado. Observa cómo reemplazar setScore(score + 1)
con setScore(s => s + 1)
corrige el botón «+3». Esto te permite poner en cola múltiples actualizaciones de estado.
import { useState } from 'react'; export default function Counter() { const [score, setScore] = useState(0); function increment() { setScore(s => s + 1); } return ( <> <button onClick={() => increment()}>+1</button> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <h1>Puntaje: {score}</h1> </> ) }
¿Listo para aprender este tema?
Lee Poner en cola una serie de actualizaciones del estado para obtener información sobre cómo poner en cola una secuencia de actualizaciones de estado.
Lee másActualizar objetos en el estado
El estado puede contener cualquier tipo de valor de JavaScript, incluidos los objetos. Pero no debes cambiar los objetos y arrays que tienes en el estado de React directamente. En cambio, cuando desees actualizar un objeto y un array, debes crear uno nuevo (o hacer una copia de uno existente) y luego actualizar el estado para usar esa copia.
Por lo general, usarás la sintaxis de propagación ...
para copiar objetos y arrays que desees cambiar. Por ejemplo, actualizar un objeto
import { useState } from 'react'; export default function Form() { const [person, setPerson] = useState({ name: 'Niki de Saint Phalle', artwork: { title: 'Nana azul', city: 'Hamburgo', image: 'https://i.imgur.com/Sd1AgUOm.jpg', } }); function handleNameChange(e) { setPerson({ ...person, name: e.target.value }); } function handleTitleChange(e) { setPerson({ ...person, artwork: { ...person.artwork, title: e.target.value } }); } function handleCityChange(e) { setPerson({ ...person, artwork: { ...person.artwork, city: e.target.value } }); } function handleImageChange(e) { setPerson({ ...person, artwork: { ...person.artwork, image: e.target.value } }); } return ( <> <label> Nombre: <input value={person.name} onChange={handleNameChange} /> </label> <label> Título: <input value={person.artwork.title} onChange={handleTitleChange} /> </label> <label> Ciudad: <input value={person.artwork.city} onChange={handleCityChange} /> </label> <label> Imagen: <input value={person.artwork.image} onChange={handleImageChange} /> </label> <p> <i>{person.artwork.title}</i> {' de '} {person.name} <br /> (ubicado en {person.artwork.city}) </p> <img src={person.artwork.image} alt={person.artwork.title} /> </> ); }
Si copiar objetos en el código se vuelve tedioso, puedes usar una biblioteca como Immer para reducir el código repetitivo:
import { useImmer } from 'use-immer'; export default function Form() { const [person, updatePerson] = useImmer({ name: 'Niki de Saint Phalle', artwork: { title: 'Nana azul', city: 'Hamburgo', image: 'https://i.imgur.com/Sd1AgUOm.jpg', } }); function handleNameChange(e) { updatePerson(draft => { draft.name = e.target.value; }); } function handleTitleChange(e) { updatePerson(draft => { draft.artwork.title = e.target.value; }); } function handleCityChange(e) { updatePerson(draft => { draft.artwork.city = e.target.value; }); } function handleImageChange(e) { updatePerson(draft => { draft.artwork.image = e.target.value; }); } return ( <> <label> Nombre: <input value={person.name} onChange={handleNameChange} /> </label> <label> Título: <input value={person.artwork.title} onChange={handleTitleChange} /> </label> <label> Ciudad: <input value={person.artwork.city} onChange={handleCityChange} /> </label> <label> Imagen: <input value={person.artwork.image} onChange={handleImageChange} /> </label> <p> <i>{person.artwork.title}</i> {' de '} {person.name} <br /> (ubicado en {person.artwork.city}) </p> <img src={person.artwork.image} alt={person.artwork.title} /> </> ); }
¿Listo para aprender este tema?
Lee Actualizar objetos en el estado para aprender cómo actualizar objetos correctamente.
Lee másActualizar arrays en el estado
Los arrays son otro tipo de objetos de JavaScript mutables que puedes almacenar en el estado y debes tratar como de solo lectura. Al igual que con los objetos, cuando deseas actualizar un array almacenado en el estado, se debe crear uno nuevo (o hacer una copia de uno existente) y luego configurar el estado para utilizar el nuevo array:
import { useState } from 'react'; let nextId = 3; const initialList = [ { id: 0, title: 'Grandes barrigas', seen: false }, { id: 1, title: 'Paisaje lunar', seen: false }, { id: 2, title: 'Guerreros de terracota', seen: true }, ]; export default function BucketList() { const [list, setList] = useState( initialList ); function handleToggle(artworkId, nextSeen) { setList(list.map(artwork => { if (artwork.id === artworkId) { return { ...artwork, seen: nextSeen }; } else { return artwork; } })); } return ( <> <h1>Lista de deseos de arte</h1> <h2>Mi lista de arte para ver:</h2> <ItemList artworks={list} onToggle={handleToggle} /> </> ); } function ItemList({ artworks, onToggle }) { return ( <ul> {artworks.map(artwork => ( <li key={artwork.id}> <label> <input type="checkbox" checked={artwork.seen} onChange={e => { onToggle( artwork.id, e.target.checked ); }} /> {artwork.title} </label> </li> ))} </ul> ); }
Si copiar arrays en el código se vuelve tedioso, puedes usar una biblioteca como Immer para reducir el código repetitivo:
import { useState } from 'react'; import { useImmer } from 'use-immer'; let nextId = 3; const initialList = [ { id: 0, title: 'Grandes barrigas', seen: false }, { id: 1, title: 'Paisaje lunar', seen: false }, { id: 2, title: 'Guerreros de terracota', seen: true }, ]; export default function BucketList() { const [list, updateList] = useImmer(initialList); function handleToggle(artworkId, nextSeen) { updateList(draft => { const artwork = draft.find(a => a.id === artworkId ); artwork.seen = nextSeen; }); } return ( <> <h1>Lista de deseos de arte</h1> <h2>Mi lista de arte para ver:</h2> <ItemList artworks={list} onToggle={handleToggle} /> </> ); } function ItemList({ artworks, onToggle }) { return ( <ul> {artworks.map(artwork => ( <li key={artwork.id}> <label> <input type="checkbox" checked={artwork.seen} onChange={e => { onToggle( artwork.id, e.target.checked ); }} /> {artwork.title} </label> </li> ))} </ul> ); }
¿Listo para aprender este tema?
Lee Actualizar arrays en el estado para aprender a actualizar arrays correctamente.
Lee más¿Qué sigue?
Dirígete a Responder a eventos para comenzar a leer este capítulo página por página.
O, si ya estás familiarizado con estos temas, ¿por qué no lees sobre Gestión del estado?