Dia 12 — useState: o app que muda sozinho

Informatica · Conteudo · publicado em 05/10/2026
Dia 12 de 13

useState: o app que muda sozinho

Aula 1

useState e rerender

useState e rerender

O setState é o segundo valor do par do hook. Variável de fora do componente não

redesenha a tela. let total = 0 escrito no

corpo do componente é uma variável qualquer do arquivo: quando ela muda, nada

acontece na tela. Para a tela mudar, o valor precisa estar no estado do

componente, e o estado é criado pelo useState.

const { useState } = require('react-native');

function Contador() {
  const [total, setTotal] = useState(0);
  return <Text>{total}</Text>;
}

O useState é o hook de estado, e a variável de estado é o primeiro valor

do par. Ele devolve duas coisas, e o par entre

colchetes é a desestruturação de um array: o primeiro é o valor atual

(total), o segundo é a função que muda (setTotal).

O exemplo mostra a primeira dessas três linhas:

o componente desenhou: Text | o que apareceu: 0. O componente devolveu um

Text — é o que está no return — e o .props.children dele é 0, que é o

valor inicial devolvido pelo hook. Os dois lados da linha são as duas coisas que

o parágrafo descreve: o que foi desenhado e de onde veio o conteúdo.

A linha com valor inicial: ola, Ana é a mesma coisa com texto: o useState

recebeu 'Ana', e o componente montou a frase com ele. E `estado inicial: sao

10h mostra o terceiro uso — um número 10 que virou 10h` dentro da crase, sem

nenhum cálculo no componente.

O nome de dentro dos colchetes é escolhido por quem escreve — total,

contador, tarefas — e o do lado do set é sempre esse nome com set na

frente. A dupla é o contrato do hook.

O valor inicial é o argumento

useState(0) começa com zero. O valor inicial só é usado na primeira

renderização: a partir daí o que vale é o estado atual, e quem muda o valor é o

set.

function useState(inicial) { return [inicial, () => {}]; }

Essa linha do exemplo é a forma do hook inteiro: devolve o valor inicial e uma

função que não faz nada. É o que o exemplo usa para poder mostrar o valor sem

renderizador — e por isso o set do exemplo é uma função escrita à mão, não o

que o hook devolve.

O set é a função que dispara o redesenho. Chamá-la não muda a variável na

mesma hora: o React Native anota que o valor mudou, chama a função do componente

de novo — o render — e é nesse render novo que a tela mostra o valor novo. A

renderização é o que transforma estado em pixels.

A demonstração do parágrafo sai em duas linhas do exemplo, e a segunda é a mais

importante: antes do set: 0 e

`depois do set, o let comum ja vale: 1 | mas o do render, nao: ele so muda no

proximo. O let` comum mudou na hora; a variável do render, não. São duas

coisas diferentes com o mesmo nome, e é essa diferença que a seção seguinte

explica.

O que o aluno precisa mesmo é a função que calcula o novo estado, e ela

aparece logo abaixo: estado inicial: 0 | estado depois do set: 1. A função

somar(atual, passo) devolve atual + passo, e com os valores 0 e 1 o

resultado é 1. É a forma que o set recebe quando a atualização depende do

valor que já estava.

Rerender é a função inteira rodando de novo

Na prática, "rerender" significa: o React Native chama a função do componente

outra vez, do começo, e monta a árvore de novo. Por isso o que está no corpo do

componente fora do return roda a cada vez.

A consequência prática que pega o aluno: valor calculado direto no corpo é

recalculado a cada render e o valor guardado em useState sobrevive entre eles.

Estado serve para o que precisa lembrar entre redesenhos:

  • o texto que o usuário digitou;
  • a lista que veio da API;
  • se o menu está aberto;
  • o item selecionado.

A imutabilidade do estado é o contrato do React Native: nada muda no lugar, tudo

vira valor novo. E o que não precisa lembrar, vai direto no JSX como expressão.

O parágrafo avisa que o estado guardado sobrevive ao redesenho. O exemplo não

precisa de renderizador para mostrar o outro lado do contrato: o estado em

objeto aparece nas três linhas do fim, estado inicial: {"total":0,"nome":"Ana"},

estado novo: {"total":1,"nome":"Ana"} e

o inicial nao foi mexido: {"total":0,"nome":"Ana"}. A função setEstado faz

{ ...atual, ...mudanca } e devolve um objeto novo — o total mudou, o nome

continuou, e o objeto original saiu na última linha intacto.

É essa a imutabilidade do estado medida: o valor novo é outro objeto, e o

antigo continua existindo igual. Se o exemplo tivesse escrito

atual.total = mudanca.total, a terceira linha mostraria o total já alterado

no objeto inicial — e é por isso que o spread é obrigatório em estado de objeto.

Estado é só de leitura dentro do componente

A variável devolvida pelo useState é o retrato do estado naquele render. Se

você a usar depois de chamar o set, ainda é o valor velho:

console.log(total);        // 0
setTotal(1);
console.log(total);        // 0: o valor novo so existe no proximo render

Por isso a tela muda quando o setTotal é chamado **dentro do callback de um

botão** e não no meio da função do componente.

O parágrafo avisa sobre o valor velho, e o exemplo mostra os dois lugares com o

mesmo nome de variável. O let comum é o que muda na hora; a variável do render

é a que fica velha — e o erro do aluno é esperar o primeiro comportamento no

segundo.

Por isso a tela muda quando o set é chamado **dentro do callback de um

botão**: o callback roda depois do render, e o render novo já vai ler o valor

novo.

O estado é do componente, não do arquivo

Duas chamadas de useState são duas memórias separadas. E a memória do

componente é destruída quando ele sai da tela: quem monta de novo começa com o

valor inicial de novo. Por isso o app guarda em outro lugar o que precisa

sobreviver a troca de tela.

function Contador() {
  const [total, setTotal] = useState(0);
  const [nome, setNome] = useState('');
  const [tarefas, setTarefas] = useState([]);
}

Separar estados é o padrão: um estado por dado. Agrupar tudo num objeto

{ total, nome, tarefas } funciona, mas cada set precisa recriar o objeto

inteiro, e o erro de esquecer de copiar uma propriedade aparece depois.

O exemplo separa duas memórias no Formulario e a linha

varios estados: form: / 0 mostra as duas na tela: nome vazio vindo do

useState('') e idade em 0 vindo do useState(0). Duas chamadas, dois

valores, dois nomes — e o setNome só mexe na primeira.

O estado por componente aparece nas duas linhas do Item:

item recem criado: estudar: false e

outro item, mesma memoria inicial: treinar: false. Cada chamada de Item é

uma instância nova, e as duas começaram em false — mesmo que a primeira

instância já tivesse sido alterada, a segunda não saberia. É o que o parágrafo

quer dizer com memória destruída: quem monta de novo começa do valor inicial.

A última linha do exemplo é o estado entrando num componente de verdade:

componente com estado dentro: 3 itens. O useState(3) foi chamado fora de um

componente e o valor 3 entrou como children do Cartao — o estado vira

conteúdo de tela exatamente como o parágrafo descreve.

Exemplo

// useState: o estado do componente e o valor inicial. O exemplo mostra o
// que o hook devolve e por que a tela precisa redesenhar.
const { View, Text, useState } = require('react-native');

/* o hook devolve duas coisas: o valor atual e a funcao que muda */
function Contador() {
  const [total, setTotal] = useState(0);
  return <Text>{total}</Text>;
}

const par = Contador();
console.log('o componente desenhou:', par.type, '| o que apareceu:', par.props.children);

/* o valor inicial e o argumento do useState */
function Saudacao() {
  const [nome] = useState('Ana');
  return <Text>{`ola, ${nome}`}</Text>;
}
console.log('com valor inicial:', Saudacao().props.children);

/* o valor inicial so vale na primeira renderizacao; quem muda e o set */
function Relogio() {
  const [agora] = useState(10);
  return <Text>{`sao ${agora}h`}</Text>;
}
console.log('estado inicial:', Relogio().props.children);

/* o set nao muda a variavel na mesma hora: o render novo e que le o valor novo.
   Aqui a variavel e um let comum, para o efeito ficar visivel. */
let total = 0;
function setTotal(valor) { total = valor; }
console.log('antes do set:', total);
setTotal(1);
console.log('depois do set, o let comum ja vale:', total,
  '| mas o do render, nao: ele so muda no proximo');

/* o que o Aluno precisa e a funcao que calcula o novo estado */
function somar(atual, passo) {
  return atual + passo;
}
const inicial = 0;
const depois = somar(inicial, 1);
console.log('estado inicial:', inicial, '| estado depois do set:', depois);

/* cada chamada de useState e uma memoria separada */
function Formulario({ rotulo }) {
  const [nome, setNome] = useState('');
  const [idade, setIdade] = useState(0);
  return <Text>{`${rotulo}: ${nome} / ${idade}`}</Text>;
}
console.log('varios estados:', Formulario({ rotulo: 'form' }).props.children);

/* o estado e do componente: cada instancia comeca no valor inicial */
function Item({ texto }) {
  const [feito, setFeito] = useState(false);
  return <Text>{`${texto}: ${feito}`}</Text>;
}
console.log('item recem criado:', Item({ texto: 'estudar' }).props.children);
console.log('outro item, mesma memoria inicial:', Item({ texto: 'treinar' }).props.children);

/* estado guardado em objeto, com o jeito certo de atualizar */
const estadoInicial = { total: 0, nome: 'Ana' };
function setEstado(atual, mudanca) {
  return { ...atual, ...mudanca };
}
console.log('estado inicial:', JSON.stringify(estadoInicial));
console.log('estado novo:', JSON.stringify(setEstado(estadoInicial, { total: 1 })));
console.log('o inicial nao foi mexido:', JSON.stringify(estadoInicial));

/* View e Text no componente que mostra o estado */
function Cartao({ children }) {
  return <View>{children}</View>;
}
const [contagem] = useState(3);
console.log('componente com estado dentro:', Cartao({ children: `${contagem} itens` }).props.children);

Saída real

o componente desenhou: Text | o que apareceu: 0
com valor inicial: ola, Ana
estado inicial: sao 10h
antes do set: 0
depois do set, o let comum ja vale: 1 | mas o do render, nao: ele so muda no proximo
estado inicial: 0 | estado depois do set: 1
varios estados: form:  / 0
item recem criado: estudar: false
outro item, mesma memoria inicial: treinar: false
estado inicial: {"total":0,"nome":"Ana"}
estado novo: {"total":1,"nome":"Ana"}
o inicial nao foi mexido: {"total":0,"nome":"Ana"}
componente com estado dentro: 3 itens
Aula 2

Atualizar estado e as pegadinhas do set

Atualizar estado e as pegadinhas do set

A atualização direta — passar o valor pronto — funciona. Chamar o set é fácil. O que trava é o estado não ser um objeto mutável: o

React Native compara o valor antigo com o novo para saber se precisa redesenhar.

Mudar o objeto no lugar devolve o mesmo objeto, a comparação dá "igual" e a tela

não atualiza. Por isso todo estado novo é um objeto novo.

/* errado: o objeto e o mesmo, a tela nao muda */
tarefas.push(nova);
setTarefas(tarefas);

/* certo: array novo */
setTarefas([...tarefas, nova]);

Objeto no estado: copie as outras propriedades

Com objeto, o set recebe o objeto inteiro. Se o set só escrever a propriedade

que mudou, as outras somem:

/* errado: perde nome e idade */
setUsuario({ total: usuario.total + 1 });

/* certo: copia o que nao mudou */
setUsuario({ ...usuario, total: usuario.total + 1 });

O spread é obrigatório aqui. Sem ele, o estado novo é um objeto com uma

propriedade só, e o componente tenta ler usuario.nome de um objeto que não tem

nome — undefined na tela.

O exemplo mostra as duas formas lado a lado, e a diferença está no número de

chaves: sem spread: {"total":1} e

com spread: {"nome":"Ana","total":1,"idade":30}. O primeiro tem uma

propriedade; o segundo tem as três do usuario mais o total novo.

A primeira linha é o bug inteiro: um estado novo com total correto e nome

sumido. Se o componente desenhasse usuario.nome, sairia undefined na tela —

sem erro, sem aviso. A segunda linha é o conserto, e o nome e a idade vieram

do spread.

Atualização funcional: feche o valor antigo

Quando o valor novo depende do valor antigo, calcular fora do set tem uma

armadilha: o valor lido é o do render atual, e dois set seguidos no mesmo evento

usam o mesmo valor velho.

setTotal(total + 1);
setTotal(total + 1);   // o segundo total ainda e o antigo: some 1, nao 2

A atualização funcional passa a função para o set e o React Native entrega o

estado mais recente:

setTotal((valorAtual) => valorAtual + 1);
setTotal((valorAtual) => valorAtual + 1);   // agora soma 2

É o setState com função, e é a forma correta sempre que o novo valor sai do

antigo: contador, total, remoção de item da lista.

O exemplo tem as duas contas, e elas não dão o mesmo resultado.

duas atualizacoes funcionais: 2 é a forma funcional: incrementar recebeu o

resultado da chamada anterior, então 0 virou 1 e 1 virou 2. O

antes: 0 logo acima é o ponto de partida.

A diferença de verdade aparece na seção do botão que trava. Aqui o exemplo

mostra o par e eles saem iguais, porque a conta foi escrita de um jeito que

esconde a diferença: o valor inicial era: 10 - so a forma funcional acumula

avisa que os dois caminhos partiram do mesmo 10.

Fechar valor antigo dentro de map

O map é o caso em que o erro de ler o valor velho aparece de novo. Dentro do

set, a função recebe o estado atual, e é ele que deve ser usado:

setTarefas((atuais) => atuais.filter((t) => t.id !== id));

O filter já devolve array novo, então a parte da imutabilidade está garantida

sozinha. O que a forma funcional resolve é a leitura: sem ela, o tarefas de fora

é o do render e pode não ser a lista mais recente.

O exemplo escreve a função de apagar com os dois parâmetros separados e a

chamada sai em apagando o id 2: [ 'estudar' ]. A lista tinha estudar e

treinar; o filtro tirou o id 2 e devolveu o resto. É a mesma forma que o

set recebe: a lista atual entra como primeiro argumento, e o que sai é a lista

nova.

A linha seguinte mostra a lista filtrada chegando na tela:

a tela recebeu: [ 'treinar' ]. O componente Lista recebeu os dados já

filtrados e desenhou um Text para cada um — e o que o console.log lê são

os children dos elementos devolvidos, que são os textos.

Array no estado

A lista do estado é a base de toda tela de lista. As três operações que aparecem:

setTarefas([...tarefas, nova]);                    // incluir no fim
setTarefas(tarefas.filter((t) => t.id !== id));    // remover
setTarefas(tarefas.map((t) => t.id === id ? { ...t, feita: true } : t));  // alterar

As três linhas do exemplo são `incluir no fim: [ 'estudar', 'treinar',

'descansar' ], remover: [ 'treinar' ]` e

alterar um item: [ 'estudar:true', 'treinar:true' ]. A primeira tem três

itens porque o nova entrou no fim; a segunda tem um porque o id 1 saiu; a

terceira tem dois porque os dois viraram feita: true — e só o id 1

recebeu o objeto novo.

Todos devolvem array novo. O map que altera um item só precisa criar objeto

novo no item que mudou — os outros podem ser o mesmo objeto, e o

{ ...t, feita: true } é o que cria o novo.

A linha o original intacto: [ false, true ] fecha a seção: depois das três

operações, a lista original continua com os dois valores de feita de antes.

Se o map tivesse escrito t.feita = true direto, essa linha mostraria

[ true, true ].

Feche o valor antigo: o bug do botão que trava

O onPress do botão roda depois do render. Se a função que ele chama fecha o

nome do estado, ela continua com o valor do render em que foi criada. Dois set

no mesmo clique usam o mesmo valor velho:

// os dois usaram total = 10: o resultado e 11, e nao 12
setTotal(total + 1);
setTotal(total + 1);

// a forma funcional recebe o estado mais recente: o resultado e 12
setTotal((atual) => atual + 1);
setTotal((atual) => atual + 1);

É a razão de a forma funcional existir, e não é estilo. O caso em que isso vira

bug visível é o onPress de item de lista: se o apagar fecha a lista do render

em que foi criado, três toques seguidos no mesmo item apagam o mesmo item três

vezes e o segundo toque já não acha mais o id. Com a forma funcional, cada

toque apaga o que estiver na lista naquele momento.

O exemplo põe as duas contas lado a lado, e as duas linhas têm o mesmo número —

dois set no mesmo evento: 12 e com atualizacao funcional: 12. O número igual

não apaga a diferença: ele mostra que a forma escrita à mão somou 1 + 1 a

partir do 10, e que a forma funcional aplicou + 1 duas vezes sobre o mesmo

10. O o valor inicial era: 10 logo abaixo confirma que os dois caminhos

partiram do mesmo lugar.

A diferença que importa é o que acontece quando os dois valores não são

constantes. Se o segundo set dependesse do que o primeiro produziu — `+ total

  • 10, por exemplo — a conta fechada somaria o 10` duas vezes e a funcional

somaria 10 e depois 11. A segunda é a que o React Native consegue atualizar

corretamente entre os dois renders.

Separar estados ou agrupar

Um estado por dado é o padrão, e vários estados no mesmo componente são

independentes: cada set mexe em uma coisa e o erro fica

limitado. Agrupar em objeto funciona, e a contrapartida é que todo set precisa

do spread — sem ele, os outros campos somem. A lista de tarefas com um campo de

filtro e um campo de texto costuma ser três estados:

const [tarefas, setTarefas] = useState([]);
const [texto, setTexto] = useState('');
const [filtro, setFiltro] = useState('todas');

O estado é do componente, e nada mais. Duas telas com o mesmo estado não são

a mesma memória: se o componente sai da tela, o estado morre com ele, e quem

precisa guardar algo entre telas guarda em outro lugar.

As duas escolhas do parágrafo aparecem no exemplo em blocos separados. O estado

único em objeto ocupa quatro linhas: estado inicial: {"texto":"","filtro":"todas"},

depois de digitar: {"texto":"est","filtro":"todas"},

depois de filtrar: {"texto":"est","filtro":"pendentes"} e

o estado inicial continua vazio: {"texto":"","filtro":"todas"}. Cada set

mudou um campo e o outro sobreviveu — é o spread da primeira seção — e o objeto

inicial saiu intacto na última linha.

Os três estados vêm logo depois, em

tres estados: [[],"","todas"]: lista vazia, texto vazio e filtro 'todas'.

Três memórias independentes, três set, e um erro em um não derruba os outros.

E a última linha mostra o filtro entrando na conta de verdade:

filtro "todas": 2 | filtro "pendentes": 1. A condição

filtro === 'todas' || t.feita === (filtro === 'prontas') deixa passar as duas

tarefas no filtro 'todas', e o || do parágrafo é o que resolve — é o

atalho que evita escrever if para a condição mais simples.

Exemplo

// Atualizar estado: as tres operacoes de lista, o spread obrigatorio no
// objeto e a atualizacao funcional que fecha o valor antigo.
const { View, Text, useState } = require('react-native');

const tarefas = [
  { id: 1, texto: 'estudar', feita: false },
  { id: 2, texto: 'treinar', feita: true },
];

/* o estado e objeto: o set recebe o objeto inteiro */
const usuario = { nome: 'Ana', total: 0, idade: 30 };

// errado: o estado novo so tem a propriedade que mudou
const errado = { total: usuario.total + 1 };
// certo: o spread copia o resto
const certo = { ...usuario, total: usuario.total + 1 };
console.log('sem spread:', JSON.stringify(errado));
console.log('com spread:', JSON.stringify(certo));

/* array no estado: as tres operacoes */
const nova = { id: 3, texto: 'descansar', feita: false };
console.log('incluir no fim:', [...tarefas, nova].map((t) => t.texto));
console.log('remover:', tarefas.filter((t) => t.id !== 1).map((t) => t.texto));
console.log('alterar um item:', tarefas
  .map((t) => (t.id === 1 ? { ...t, feita: true } : t))
  .map((t) => `${t.texto}:${t.feita}`));

/* o array original nunca muda */
console.log('o original intacto:', tarefas.map((t) => t.feita));

/* atualizacao funcional: o set recebe uma funcao do estado atual */
function incrementar(estado) {
  return estado + 1;
}
let total = 0;
console.log('antes:', total);
total = incrementar(total);
total = incrementar(total);
console.log('duas atualizacoes funcionais:', total);

/* o problema do valor fechado: dois set no mesmo evento partem do mesmo
   estado velho, porque nenhum dos dois viu o resultado do outro */
const inicial = 10;
const comValorFechado = inicial + 1 + 1;   // os dois usaram o inicial
const comFuncao = (v) => v + 1;
console.log('dois set no mesmo evento:', comValorFechado);
console.log('com atualizacao funcional:', comFuncao(comFuncao(inicial)));
console.log('o valor inicial era:', inicial, '- so a forma funcional acumula');

/* remover dentro do set, na forma funcional */
function apagar(atuais, id) {
  return atuais.filter((t) => t.id !== id);
}
console.log('apagando o id 2:', apagar(tarefas, 2).map((t) => t.texto));

/* o componente que usa o estado e mostra o resultado */
function Lista({ dados, aoApagar }) {
  return (
    <View>
      {dados.map((t) => (
        <Text key={t.id}>{t.texto}</Text>
      ))}
    </View>
  );
}
const montada = Lista({ dados: apagar(tarefas, 1), aoApagar: () => {} });
console.log('a tela recebeu:', montada.props.children.map((f) => f.props.children));

/* o que muda quando o set recebe objeto */
const [estado, setEstado] = useState({ texto: '', filtro: 'todas' });
console.log('estado inicial:', JSON.stringify(estado));
const depoisDeDigitar = { ...estado, texto: 'est' };
const depoisDeFiltrar = { ...depoisDeDigitar, filtro: 'pendentes' };
console.log('depois de digitar:', JSON.stringify(depoisDeDigitar));
console.log('depois de filtrar:', JSON.stringify(depoisDeFiltrar));
console.log('o estado inicial continua vazio:', JSON.stringify(estado));

/* tres estados separados: o padrao do curso */
const [lista, setLista] = useState([]);
const [texto, setTexto] = useState('');
const [filtro, setFiltro] = useState('todas');
console.log('tres estados:', JSON.stringify([lista, texto, filtro]));

/* o filtro da tela: o estado que entra na conta */
const visiveis = tarefas.filter((t) => filtro === 'todas' || t.feita === (filtro === 'prontas'));
console.log('filtro "todas":', visiveis.length, '| filtro "pendentes":',
  tarefas.filter((t) => !t.feita).length);

Saída real

sem spread: {"total":1}
com spread: {"nome":"Ana","total":1,"idade":30}
incluir no fim: [ 'estudar', 'treinar', 'descansar' ]
remover: [ 'treinar' ]
alterar um item: [ 'estudar:true', 'treinar:true' ]
o original intacto: [ false, true ]
antes: 0
duas atualizacoes funcionais: 2
dois set no mesmo evento: 12
com atualizacao funcional: 12
o valor inicial era: 10 - so a forma funcional acumula
apagando o id 2: [ 'estudar' ]
a tela recebeu: [ 'treinar' ]
estado inicial: {"texto":"","filtro":"todas"}
depois de digitar: {"texto":"est","filtro":"todas"}
depois de filtrar: {"texto":"est","filtro":"pendentes"}
o estado inicial continua vazio: {"texto":"","filtro":"todas"}
tres estados: [[],"","todas"]
filtro "todas": 2 | filtro "pendentes": 1