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. Puede agregar estado a cualquier componente y actualizarlo según sea necesario. En este capítulo, aprenderá a escribir componentes que manejen interacciones, actualicen su estado y muestren resultados diferentes a lo largo del tiempo.

Respondiendo a eventos

React le permite agregar controladores de eventos a su JSX. Los controladores de eventos son sus propias funciones que se activarán en respuesta a las interacciones del usuario, como hacer clic, pasar el mouse, enfocarse en las entradas del formulario, etc.

Los componentes integrados como <button> solo admiten eventos de navegador integrados como onClick. Sin embargo, también puede crear sus propios componentes y dar a sus accesorios de controlador de eventos los nombres específicos de la aplicación que desee.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Jugando!')}
      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?

Lea Responder a eventos para aprender cómo agregar controladores de eventos..

Lee más

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.

Puede agregar estado a un componente con un useState Hook. Los Hooks son funciones especiales que permiten que sus componentes usen funciones de React (el estado es una de esas funciones). useState Hook 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 le 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>
        de {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Esconder' : 'Mostrar'} detalles
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

¿Listo para aprender este tema?

Lea El estado: la memoria de un componente para aprender a recordar un valor y actualizarlo en la interacción.

Lee más

Renderizar y confirmar

Antes de que sus componentes se muestren en la pantalla, deben ser renderizados por React. Comprender los pasos de este proceso lo ayudará a pensar en cómo se ejecuta su código y explicar su comportamiento.

Imagine que sus componentes son cocineros en la cocina, ensamblando sabrosos platos a partir de ingredientes. En este escenario, React es el camarero que presenta las solicitudes de los clientes y les trae sus pedidos. Este proceso de solicitud y servicio de UI consta de tres pasos:

  1. Desencadenante un render (entregar el pedido del comensal a la cocina)
  2. Renderizando el componente (preparando el pedido en la cocina)
  3. Confirmando el DOM (colocando el pedido en la mesa)
  1. React como un mesero en un restaurante, obteniendo pedidos de los usuarios y entregándolos a la cocina de componentes.
    Trigger
  2. La Tarjeta Chef le da a React un nuevo componente de tarjeta.
    Renderizado
  3. React entrega la Tarjeta al usuario en su mesa.
    Confirmación

Illustrated by Rachel Lee Nabors

¿Listo para aprender este tema?

Lea Renderizado y confirmación para conocer el ciclo de vida de una actualización de la interfaz de usuario.

Lee más

Estado como una instantánea

A diferencia de las variables regulares de JavaScript, el estado React se comporta más como una instantánea. Configurarlo no cambia la variable de estado que ya tiene, sino que activa una nueva representación. ¡Esto puede ser sorprendente al principio!

console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!

Este comportamiento lo 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(`Usted le dijo ${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="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Enviar</button>
    </form>
  );
}

¿Listo para aprender este tema?

Lea Estado como instantánea para saber por qué el estado aparece «fijo» y sin cambios dentro de los controladores de eventos.

Lee más

Poner 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>
    </>
  )
}

Estado como instantánea explica por qué sucede esto. El estado de configuración solicita una nueva representación, 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

Puede solucionar esto pasando una función de actualización al configurar el estado. Observe cómo reemplazar setScore(score + 1) con setScore(s => s + 1) corrige el botón «+3». Esto le 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?

Lea Poner en cola una serie de actualizaciones de estado para obtener información sobre cómo poner en cola una secuencia de actualizaciones de estado.

Lee más

Actualizar objetos en estado

El estado puede contener cualquier tipo de valor de JavaScript, incluidos los objetos. Pero no debe cambiar los objetos y arreglos que tiene en el estado de React directamente. En cambio, cuando desea actualizar un objeto y un arreglo, debe crear uno nuevo (o hacer una copia de uno existente) y luego actualizar el estado para usar esa copia.

Por lo general, usará la sintaxis de extensión ... para copiar objetos y matrices que desea cambiar. Por ejemplo, actualizar un objeto anidado podría verse así:

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      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>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

Si copiar objetos en el código se vuelve tedioso, puede 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: 'Blue Nana',
      city: 'Hamburg',
      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>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

¿Listo para aprender este tema?

Lea Actualizar objetos en el estado para aprender cómo actualizar objetos correctamente.

Lee más

Actualización de arreglos en estado

Los arreglos son otro tipo de objetos de JavaScript mutables que puede almacenar en el estado y debe tratar como de solo lectura. Al igual que con los objetos, cuando desea actualizar un arreglo almacenado en el estado, se debe crear una nueva (o hacer una copia de una existente) y luego configurar el estado para utilizar el nuevo arreglo:

import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', 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 cubo 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 arreglos en el código se vuelve tedioso, puede 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: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', 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>Art Bucket List</h1>
      <h2>My list of art to see:</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?

Lea Actualización de arreglos en estado para aprender a actualizar arreglos correctamente.

Lee más

¿Que sigue?

Dirígete a Responder a eventos para comenzar a leer este capítulo página por página.

O, si ya está familiarizado con estos temas, ¿por qué no lee sobre Gestión del estado?