State: A Memória de um Componente

Componentes comumente 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” deve colocar um produto no carrinho. Componentes precisam “lembrar” de coisas: o valor de entrada atual, a imagem atual, o carrinho de compras. Em React, esse tipo de memória componente-específica é chamado de state.

Você aprenderá

  • Como adicionar uma variável de state com o Hook useState
  • Qual o par de valores retornado pelo Hook useState
  • Como adicionar mais de uma variável de state
  • Por que o state é chamado de local

Quando uma variável comum não é o suficiente

Aqui está um componente que renderiza a imagem de uma escultura. Ao clicar no botão “Next” ele deveria mostrar a próxima escultura mudando o index para 1, então 2, e assim por diante. Entretanto, isso não funcionará (você pode tentar!):

import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

O manipulador de eventos handleClick está atualizando a variável local, index. Mas duas coisas previnem essa mudança de ser visível:

  1. Variáveis locais não persistem entre renderizações. Quando o React renderiza esse componente uma segunda vez, ele o faz do princípio—sem considerar quaisquer mudanças às variáveis locais.
  2. Mudanças às variáveis locais não acionam renderizações. O React não percebe que precisa renderizar o componente novamente com os novos dados.

Para atualizar um componente com novos dados, duas coisas precisam acontecer:

  1. Reter os dados entre renderizações.
  2. Acionar o React para renderizar o componente com os novos dados (rerrenderização).

O Hook useState provê essas duas coisas:

  1. Uma Variável de state para reter os dados entre renderizações.
  2. Uma função de definição de state para atualizar a variável e acionar o React para renderizar o componente novamente.

Adicionando uma variável de estado

Para adicionar uma variável de estado, importe useState do React no topo do arquivo:

import { useState } from 'react';

Então, substitua essa linha:

let index = 0;

com

const [index, setIndex] = useState(0);

index é uma variável de estado e setIndex é a função de definição.

A sintaxe [ e ] aqui é chamada de desestruturação de array e ela permite que você leia valores de um array. O array retornado pelo useState sempre possui exatamente dois itens.

Essa é a maneira como eles trabalham juntos em handleClick:

function handleClick() {
setIndex(index + 1);
}

Agora clicar no botão “Next” muda a escultura atual:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Conheça seu primero Hook

No React, useState, assim como qualquer outra função iniciada com “use”, é denominada de Hook.

Hooks são funções especiais que estão disponíveis somente enquanto o React está renderizando (as quais nós entraremos em mais detalhes na próxima página). Eles permitem que você “se conecte” a diferentes recursos do React.

State é só um destes recursos, mas você irá conhecer os outros Hooks mais tarde.

Pitfall

Hooks—funções iniciadas com use—só podem ser chamadas no nível superior dos seus componentes ou em seus próprios Hooks. Você não pode chamar Hooks dentro de condições, loops, ou outras funções aninhadas. Hooks são funções, mas é útil pensar neles como declarações incondicionais sobre as necessidades do seu componente. Você “usa” recursos do React no topo de seu componente similarmente a como você “importa” módulos no topo de seu arquivo.

Anatomia do useState

Ao chamar useState, você está dizendo ao React que você quer que esse componente lembre-se de algo:

const [index, setIndex] = useState(0);

Nesse caso, você quer que o React lembre-se de index.

Note

A convenção é nomear esse par como const [algo, setAlgo]. Você poderia nomeá-lo de qualquer outra coisa, mas convenções tornam as coisas mais fáceis de se entender entre projetos.

O único argumento para o useState é o valor inicial da sua variável de state. Nesse exemplo, o valor inicial de index é definido como 0 com useState(0).

Toda vez que seu componente é renderizado, useState lhe dá um array contendo dois valores:

  1. A variável de state (index) com o valor que você armazenou.
  2. A função de definição de state (setIndex) a qual pode atualizar a variável de state e acionar o React para renderizar o componente novamente.

Aqui está como isso ocorre na prática:

const [index, setIndex] = useState(0);
  1. Seu componente renderiza pela primeira vez. Porque você passou 0 ao useState como o valor inicial de index, ele retornará [0, setIndex]. O React lembra-se de que 0 é o valor de state mais recente.
  2. Você atualiza o state. Quando um usuário clica no botão, ele chama setIndex(index + 1). index é 0, logo setIndex(1). Isto diz ao React para se lembrar que index é 1 a partir de agora e aciona outra renderização.
  3. A segunda renderização do seu componente. O React ainda vê useState(0), mas porque ele lembra que você definiu index como 1, retorna [1, setIndex] em vez disto.
  4. E assim por diante!

Dando a um componente múltiplas variáveis de state

Você pode ter quantas variáveis de state de quantos tipos quiser em um componente. Esse componente tem duas variáveis de state, um número index e um boleano showMore o qual é acionado quando você clica em “Show details”:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}

É uma boa ideia ter múltiplas variáveis de state se estas não for relacionadas, como index e showMore nesse exemplo. Mas se você perceber que frequentemente altera duas variáveis de estado juntas, pode ser mais fácil combiná-las em uma. Por exemplo, se você tem um formulário com vários campos, é mais conveniente ter uma única variável de state a qual armazena um objeto do que uma variável por campo. Leia Escolhendo a Estrutura do State para mais dicas.

Deep Dive

Como o React sabe qual state retornar?

Você pode ter notado que a chamada ao useState não recebe nenhuma informação sobre a qual variável de state ela se refere. Não há um “identificador” que é passado ao useState, então como este sabe qual dessas variáveis de state retornar? Depende de alguma magia como analisar suas funções? A resposta é não.

Em vez disso, para viabilizar sintaxe concisa, os Hooks dependem em uma chamada estável feita em toda renderização do mesmo componente. Isso funciona bem na prática porque se você seguir a regra acima (“só chamar Hooks no nível do topo”), os Hooks sempre serão chamados na mesma ordem. Adicionalmente, um plugin de linter pega a maioria dos erros.

Internamente, o React armazena um array de pares de state para cada componente. Ele também mantêm o índice do par atual, o qual é definido como 0 antes da renderização. A cada vez que você chama useState, o React lhe dá o próximo par de state e incrementa o índice. Você pode ler mais sobre esse mecanismo em React Hooks: Não é Mágica, Apenas Arrays.

Esse exemplo não usa React mas dá uma ideia de como o useState funciona internamente:

let componentHooks = [];
let currentHookIndex = 0;

// Como o useState funciona dentro do React (simplificado).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // Essa não é nossa primeira renderização,
    // então o par de state já existe.
    // Retorne-o e prepare-se para a próxima chamada do Hook.
    currentHookIndex++;
    return pair;
  }

  // Essa é a primeira vez em que estamos renderizando,
  // então crie um par de state e o salve.
  pair = [initialState, setState];

  function setState(nextState) {
    // Quando o usuário requisitar uma mudança de state,
    // ponha o novo valor no par.
    pair[0] = nextState;
    updateDOM();
  }

  // Salve o par para futuras renderizações
  // e prepare-se para a próxima chamada do Hook.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

function Gallery() {
  // Cada chamada ao useState() irá obter o próximo par.
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  // Esse exemplo não usa React, então
  // retorne um objeto de saída em vez de JSX.
  return {
    onNextClick: handleNextClick,
    onMoreClick: handleMoreClick,
    header: `${sculpture.name} by ${sculpture.artist}`,
    counter: `${index + 1} of ${sculptureList.length}`,
    more: `${showMore ? 'Hide' : 'Show'} details`,
    description: showMore ? sculpture.description : null,
    imageSrc: sculpture.url,
    imageAlt: sculpture.alt
  };
}

function updateDOM() {
  // Reinicialize o índice atual do Hook
  // antes de renderizar o componente.
  currentHookIndex = 0;
  let output = Gallery();

  // Atualize a DOM para igualar-se à saída.
  // Essa é a parte que o React faz para você.
  nextButton.onclick = output.onNextClick;
  header.textContent = output.header;
  moreButton.onclick = output.onMoreClick;
  moreButton.textContent = output.more;
  image.src = output.imageSrc;
  image.alt = output.imageAlt;
  if (output.description !== null) {
    description.textContent = output.description;
    description.style.display = '';
  } else {
    description.style.display = 'none';
  }
}

let nextButton = document.getElementById('nextButton');
let header = document.getElementById('header');
let moreButton = document.getElementById('moreButton');
let description = document.getElementById('description');
let image = document.getElementById('image');
let sculptureList = [{
  name: 'Homenaje a la Neurocirugía',
  artist: 'Marta Colvin Andrade',
  description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.',
  url: 'https://i.imgur.com/Mx7dA2Y.jpg',
  alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.'  
}, {
  name: 'Floralis Genérica',
  artist: 'Eduardo Catalano',
  description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.',
  url: 'https://i.imgur.com/ZF6s192m.jpg',
  alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.'
}, {
  name: 'Eternal Presence',
  artist: 'John Woodrow Wilson',
  description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."',
  url: 'https://i.imgur.com/aTtVpES.jpg',
  alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.'
}, {
  name: 'Moai',
  artist: 'Unknown Artist',
  description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.',
  url: 'https://i.imgur.com/RCwLEoQm.jpg',
  alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.'
}, {
  name: 'Blue Nana',
  artist: 'Niki de Saint Phalle',
  description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.',
  url: 'https://i.imgur.com/Sd1AgUOm.jpg',
  alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.'
}, {
  name: 'Ultimate Form',
  artist: 'Barbara Hepworth',
  description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.',
  url: 'https://i.imgur.com/2heNQDcm.jpg',
  alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.'
}, {
  name: 'Cavaliere',
  artist: 'Lamidi Olonade Fakeye',
  description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
  url: 'https://i.imgur.com/wIdGuZwm.png',
  alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.'
}, {
  name: 'Big Bellies',
  artist: 'Alina Szapocznikow',
  description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
  url: 'https://i.imgur.com/AlHTAdDm.jpg',
  alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.'
}, {
  name: 'Terracotta Army',
  artist: 'Unknown Artist',
  description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.',
  url: 'https://i.imgur.com/HMFmH6m.jpg',
  alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.'
}, {
  name: 'Lunar Landscape',
  artist: 'Louise Nevelson',
  description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.',
  url: 'https://i.imgur.com/rN7hY6om.jpg',
  alt: 'A black matte sculpture where the individual elements are initially indistinguishable.'
}, {
  name: 'Aureole',
  artist: 'Ranjani Shettar',
  description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."',
  url: 'https://i.imgur.com/okTpbHhm.jpg',
  alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.'
}, {
  name: 'Hippos',
  artist: 'Taipei Zoo',
  description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.',
  url: 'https://i.imgur.com/6o5Vuyu.jpg',
  alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.'
}];

// Faça a UI igualar-se ao state inicial.
updateDOM();

Você não precisa entender isto para usar React, mas você pode considerá-lo um modelo mental útil.

State é isolado e privado

State é local a uma instância do componente na tela. Em outras palavras, se você renderiza o mesmo componente duas vezes, cada cópia terá state completamente isolado! Alterar um deles não irá afetar o outro.

Nesse exemplo, o componente Gallery de antes é renderizado duas vezes sem mudanças a sua lógica. Tente clicar nos botões dentro de cada uma das galerias. Note que seus states são independentes:

import Gallery from './Gallery.js';

export default function Page() {
  return (
    <div className="Page">
      <Gallery />
      <Gallery />
    </div>
  );
}

Isto é o que faz do state diferente de variáveis comuns que você pode declarar no topo de seu módulo. State não é atrelado a uma chamada de função em particular ou a um lugar no código, mas sim é “local” a um lugar específico na tela. Você renderizou dois componentes <Gallery />, então seus states são armazenados separadamente.

Perceba também como o componente Page não “sabe” nada sobre o state de Gallery ou se até ele tem algum. Diferentemente das props, o state é completamente privado ao componente que o declara. O componente pai não pode alterá-lo. Isso permite que você adicione state a qualquer componente ou remova-o sem impactar o restante dos componentes.

E se você quisesse que ambas as galerias mantivessem seus states sincronizados? A maneira correta de fazer isso em React se dá por meio da remoção do state dos componentes filhos e armazenando-o no componente pai comum mais próximo a eles. As próximas páginas focarão na organização do state de um único componente, mas nós iremos voltar a este assunto em Compartilhando State Entre Componentes.

Recap

  • Use uma variável de state quando um componente precisa “lembrar” de alguma informação entre renderizações.
  • Variáveis de state são declaradas chamando o Hook useState.
  • Hooks são funções especiais que começam com use. Eles permitem que você “se conecte” a recursos do React como o state.
  • Hooks podem lembrá-lo de importações: eles precisam se chamados incondicionalmente. Chamar Hooks, incluindo useState, só é válido no nível do topo de um componente ou em outro Hook.
  • O Hook useState retorna um par de valores: o state atual e a função para atualizá-lo.
  • Você pode ter mais de uma variável de state. Internamente, o React as combina por sua ordem.
  • State é privado ao componente. Se você o renderizar em dois lugares, cada cópia recebe seu próprio state.

Quando você pressionar “Next” na última escultura, o código falha. Conserte a lógica para prevenir a falha. Você pode fazer isso adicionado lógica extra ao manipulador de eventos ou desabilitando o botão quando a ação não é possível.

Após consertar a falha, adicione um botão “Previous” que mostra a escultura anterior. Ele não deve falhar na primeira escultura.

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}