Adicionando Interatividade
Algumas coisas na tela são atualizadas em resposta à entrada do usuário. Por exemplo, clicar em uma galeria de imagens muda a imagem ativa. No React, os dados que mudam ao longo do tempo são chamados de estado. Você pode adicionar estado a qualquer componente e atualizá-lo conforme necessário. Neste capítulo, você aprenderá como escrever componentes que tratam interações, atualizam seu estado e exibem diferentes saídas ao longo do tempo.
Neste capítulo
- Como lidar com eventos iniciados pelo usuário
- Como fazer os componentes “lembrar” informações com estado
- Como o React atualiza a UI em duas fases
- Por que o estado não é atualizado logo após você alterá-lo
- Como enfileirar várias atualizações de estado
- Como atualizar um objeto no estado
- Como atualizar um array no estado
Respondendo a eventos
O React permite que você adicione manipuladores de eventos ao seu JSX. Manipuladores de eventos são suas próprias funções que serão acionadas em resposta a interações do usuário, como clicar, pairar, focar em entradas de formulário, e assim por diante.
Componentes embutidos como <button>
só suportam eventos de navegador integrados, como onClick
. No entanto, você também pode criar seus próprios componentes e dar aos props do manipulador de eventos nomes específicos de aplicação que desejar.
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Tocando!')} onUploadImage={() => alert('Carregando!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Reproduzir Filme </Button> <Button onClick={onUploadImage}> Carregar Imagem </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Ready to learn this topic?
Leia Respondendo a Eventos para aprender como adicionar manipuladores de eventos.
Read MoreEstado: a memória de um componente
Os componentes frequentemente precisam mudar o que está na tela como resultado de uma interação. Digitar em um formulário deve atualizar o campo de entrada, clicar em “próximo” em um carrossel de imagens deve mudar qual imagem é exibida, clicar em “comprar” coloca um produto no carrinho de compras. Os componentes precisam “lembrar” de coisas: o valor atual de entrada, a imagem atual, o carrinho de compras. No React, esse tipo de memória específica do componente é chamado de estado.
Você pode adicionar estado a um componente com um Hook useState
. Os Hooks são funções especiais que permitem que seus componentes utilizem recursos do React (o estado é um desses recursos). O Hook useState
permite que você declare uma variável de estado. Ele recebe o estado inicial e retorna um par de valores: o estado atual e uma função de configuração de estado que permite atualizá-lo.
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
Aqui está como uma galeria de imagens usa e atualiza o estado ao clicar:
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}> Próximo </button> <h2> <i>{sculpture.name} </i> por {sculpture.artist} </h2> <h3> ({index + 1} de {sculptureList.length}) </h3> <button onClick={handleMoreClick}> {showMore ? 'Ocultar' : 'Mostrar'} detalhes </button> {showMore && <p>{sculpture.description}</p>} <img src={sculpture.url} alt={sculpture.alt} /> </> ); }
Ready to learn this topic?
Leia Estado: A Memória de um Componente para aprender como lembrar um valor e atualizá-lo na interação.
Read MoreRenderizar e confirmar
Antes que seus componentes sejam exibidos na tela, eles devem ser renderizados pelo React. Compreender as etapas desse processo ajudará você a pensar sobre como seu código é executado e explicar seu comportamento.
Imagine que seus componentes são cozinheiros na cozinha, montando pratos saborosos a partir dos ingredientes. Neste cenário, o React é o garçom que recebe pedidos dos clientes e leva seus pedidos. Este processo de solicitar e servir UI tem três etapas:
- Acionando uma renderização (entregando o pedido do cliente à cozinha)
- Renderizando o componente (preparando o pedido na cozinha)
- Confirmando no DOM (colocando o pedido na mesa)
Acionar Renderizar Confirmar
Illustrated by Rachel Lee Nabors
Ready to learn this topic?
Leia Renderizar e Confirmar para aprender sobre o ciclo de vida de uma atualização de UI.
Read MoreEstado como um instantâneo
Ao contrário das variáveis JavaScript regulares, o estado do React se comporta mais como um instantâneo. Configurá-lo não muda a variável de estado que você já possui, mas sim, aciona uma nova renderização. Isso pode ser surpreendente a princípio!
console.log(count); // 0
setCount(count + 1); // Solicitar uma nova renderização com 1
console.log(count); // Ainda 0!
Esse comportamento ajuda você a evitar erros sutis. Aqui está um pequeno aplicativo de chat. Tente adivinhar o que acontece se você pressionar “Enviar” primeiro e depois mudar o destinatário para Bob. Qual nome aparecerá no alert
cinco segundos depois?
import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('Alice'); const [message, setMessage] = useState('Olá'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`Você disse ${message} para ${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="Mensagem" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">Enviar</button> </form> ); }
Ready to learn this topic?
Leia Estado como um Instantâneo para aprender por que o estado aparece “fixo” e inalterado dentro dos manipuladores de eventos.
Read MoreEnfileirando uma série de atualizações de estado
Este componente tem um bug: clicar em “+3” incrementa a pontuação apenas uma 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>Pontuação: {score}</h1> </> ) }
Estado como um Instantâneo explica por que isso está acontecendo. Configurar o estado solicita uma nova renderização, mas não o muda no código que já está sendo executado. Assim, score
continua sendo 0
logo após você chamar 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
Você pode corrigir isso passando uma função de atualização ao definir o estado. Observe como substituir setScore(score + 1)
por setScore(s => s + 1)
corrige o botão “+3”. Isso permite que você enfileire múltiplas atualizações 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>Pontuação: {score}</h1> </> ) }
Ready to learn this topic?
Leia Enfileirando uma Série de Atualizações de Estado para aprender como enfileirar uma sequência de atualizações de estado.
Read MoreAtualizando objetos no estado
O estado pode conter qualquer tipo de valor JavaScript, incluindo objetos. Mas você não deve mudar objetos e arrays que você mantém no estado do React diretamente. Em vez disso, quando você quiser atualizar um objeto ou array, precisa criar um novo (ou fazer uma cópia de um existente) e, em seguida, atualizar o estado para usar essa cópia.
Normalmente, você usará a sintaxe de expansão ...
para copiar objetos e arrays que deseja alterar. Por exemplo, atualizar um objeto aninhado pode ser assim:
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> Nome: <input value={person.name} onChange={handleNameChange} /> </label> <label> Título: <input value={person.artwork.title} onChange={handleTitleChange} /> </label> <label> Cidade: <input value={person.artwork.city} onChange={handleCityChange} /> </label> <label> Imagem: <input value={person.artwork.image} onChange={handleImageChange} /> </label> <p> <i>{person.artwork.title}</i> {' por '} {person.name} <br /> (localizada em {person.artwork.city}) </p> <img src={person.artwork.image} alt={person.artwork.title} /> </> ); }
Se copiar objetos em código se torna tedioso, você pode usar uma biblioteca como Immer para reduzir o código repetitivo:
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Ready to learn this topic?
Leia Atualizando Objetos no Estado para aprender como atualizar objetos corretamente.
Read MoreAtualizando arrays no estado
Arrays são outro tipo de objetos JavaScript mutáveis que você pode armazenar no estado e devem ser tratados como somente leitura. Assim como com objetos, quando você quiser atualizar um array armazenado no estado, você precisa criar um novo (ou fazer uma cópia de um existente) e, em seguida, definir o estado para usar o novo array:
import { useState } from 'react'; 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 Arte</h1> <h2>Minha 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> ); }
Se copiar arrays em código se torna tedioso, você pode usar uma biblioteca como Immer para reduzir o código repetitivo:
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Ready to learn this topic?
Leia Atualizando Arrays no Estado para aprender como atualizar arrays corretamente.
Read MoreO que vem a seguir?
Vá para Respondendo a Eventos para começar a ler este capítulo página por página!
Ou, se você já está familiarizado com esses tópicos, por que não ler sobre Gerenciando Estado?