Como tipar o useState do React com TypeScript

Neste artigo você aprenderá a como tipar o useState do React, utilizando o superset de JavaScript: o TypeScript!

tipar o useState do React capa

Fala programador(a), beleza? Bora aprender mais sobre React JS!

TypeScript trouxe ao mundo JavaScript uma dimensão adicional: a tipagem estática.

E com a popularização do React, era apenas uma questão de tempo até essas duas poderosas ferramentas convergirem.

Uma área em que vemos essa intersecção é ao usar hooks, especificamente o useState, no React com TypeScript.

Neste artigo, vamos explorar como você pode tipar com precisão o hook useState do React ao trabalhar com TypeScript, permitindo que você tire o máximo proveito de ambos.

Por que tipar o useState?

O TypeScript é um superconjunto do JavaScript que permite a adição de tipos. Isso pode parecer um pouco desnecessário no início, mas quando sua aplicação cresce, os benefícios se tornam inestimáveis.

Com a tipagem, você pode evitar muitos erros em tempo de compilação que, de outra forma, só seriam detectados em tempo de execução.

Ao usar o useState com TypeScript, você está essencialmente fornecendo ao compilador informações sobre o tipo de estado que você está tentando gerenciar.

Isso garante que você não tente, acidentalmente, atualizar o estado com um tipo de dado incompatível.

Começando com a tipagem básica

Vamos começar com um exemplo simples para demonstrar a tipagem básica do useState.

Exemplo:

import React, { useState } from 'react';

const MeuComponente: React.FC = () => {
  const [contador, setContador] = useState<number>(0);

  return (
    <div>
      <p>O contador está em: {contador}</p>
      <button onClick={() => setContador(contador + 1)}>Incrementar</button>
    </div>
  );
}

Neste exemplo, estamos usando o useState para gerenciar um contador. Explicitamente declaramos que o estado deve ser do tipo number ao usar <number>.

Lidando com objetos complexos

Na prática, você pode encontrar situações em que o estado que está gerenciando não é tão simples quanto um número ou uma string. Pode ser um objeto com várias propriedades. Vejamos como tipar isso.

Exemplo:

import React, { useState } from 'react';

interface Usuario {
  nome: string;
  idade: number;
}

const MeuComponente: React.FC = () => {
  const [usuario, setUsuario] = useState<Usuario>({ nome: 'João', idade: 30 });

  return (
    <div>
      <p>{usuario.nome} tem {usuario.idade} anos de idade.</p>
    </div>
  );
}

Aqui, definimos uma interface Usuario e informamos ao useState que ele deve esperar um estado que se ajuste à forma desta interface.

Lidando com estados que podem ser nulos ou indefinidos

Em alguns casos, você pode ter um estado que começa como nulo ou indefinido e depois é preenchido. TypeScript tem você coberto aqui também.

Exemplo:

import React, { useState } from 'react';

const MeuComponente: React.FC = () => {
  const [nome, setNome] = useState<string | null>(null);

  return (
    <div>
      {nome ? <p>Olá, {nome}!</p> : <p>Nome não fornecido.</p>}
    </div>
  );
}

Usando o operador |, informamos ao TypeScript que o estado pode ser uma string ou nulo.

Quer aprender mais sobre programação? Conheça nosso canal no YouTube:

Conclusão

Com a crescente popularidade do TypeScript no desenvolvimento React, é vital que os desenvolvedores entendam como tipar corretamente seus estados para obter o máximo dos benefícios que ambos oferecem.

O hook useState, sendo uma parte fundamental do React, é um excelente ponto de partida para mergulhar no mundo da tipagem no React com TypeScript.

Se você ainda não está usando TypeScript com React, esperamos que este guia sirva como um incentivo para começar.

A segurança e clareza adicionais que você obtém ao tipar seus estados e props farão uma diferença significativa à medida que sua aplicação cresce e se torna mais complexa.

Está buscando em evoluir como Programador? Confira o nossos cursos de programação.

Precisa mesmo do <number>? Entendendo a inferência:

Antes de sair tipando tudo na mão, se liga numa coisa: o TypeScript olha o valor inicial e deduz o tipo sozinho

// contador é number, setContador só aceita number

Aqui o <number> seria opcional, o 0 já entrega o recado

Então quando vale a pena escrever o generic?

Quando o valor inicial NÃO conta a história toda:

  • quando o estado começa vazio e depois recebe outra coisa
  • quando o estado pode ter mais de um tipo (string ou null, por exemplo)
  • quando o valor inicial é genérico demais, tipo um array vazio ou um objeto vazio

É a mesma ideia de declarar o retorno de uma função mesmo dando pra inferir: você escreve pra travar a intenção, não pra ensinar o compilador 🙂

Atualizando só uma propriedade do objeto sem quebrar a tipagem:

O exemplo do usuário ali em cima funciona, mas na vida real você quase sempre mexe em um campo por vez

E aqui mora uma pegadinha: o useState não faz merge automático como o setState das classes, o que você passar SUBSTITUI o estado inteiro

setUsuario((anterior) => ({ ...anterior, idade: 31 }))

Esse espalhamento do estado anterior é o que mantém o objeto completo e o tipo intacto

Se você mandar só { idade: 31 }, o TypeScript avisa que está faltando nome

E isso é exatamente o tipo de erro que ele existe pra pegar antes de você descobrir em produção 😀

Como tipar um array no useState:

Esse é o caso que mais pega gente de surpresa

Parece inofensivo, mas o array vazio não tem nenhuma pista de tipo dentro dele

Aí quando você tenta adicionar um item, o TypeScript reclama, porque ele entendeu aquilo como um array que nunca vai receber nada

A correção é dizer o que mora dentro do array:

interface Tarefa {
id: number
titulo: string
feita: boolean
}

function adicionar(nova: Tarefa) {
}

Tome cuidado com array vazio sem generic, é daquelas coisas que só dão as caras lá na frente

Tipando um estado que vem de uma requisição:

Aqui é onde as três coisas que vimos se juntam: objeto, null e generic

O estado começa vazio porque o dado ainda não chegou, e depois ele vira um objeto completo

interface Usuario {
nome: string
idade: number
}

function Perfil() {

useEffect(() => {
buscarUsuario()
.then((dados) => setUsuario(dados))
.finally(() => setCarregando(false))

if (carregando) return <p>Carregando...</p>
if (!usuario) return <p>Nada por aqui</p>

return <p>{usuario.nome} tem {usuario.idade} anos</p>
}

Sacou a jogada? Depois daqueles dois if, o TypeScript já sabe que usuario não é mais nulo, e você acessa usuario.nome sem reclamação nenhuma

Isso chama estreitamento de tipo, e é o motivo real de usar Tipo | null em vez de sair inventando objeto vazio só pra calar o compilador

Como passar o setState para um componente filho:

Cedo ou tarde você vai querer mandar a função de atualizar pra outro componente

E aí bate a dúvida: qual é o tipo dessa função?

O React já exporta ele pronto:

import React, { useState, Dispatch, SetStateAction } from 'react'

interface CampoProps {
valor: string
aoMudar: Dispatch<SetStateAction<string>>
}

function Campo({ valor, aoMudar }: CampoProps) {
return <input value={valor} onChange={(e) => aoMudar(e.target.value)} />
}

function Formulario() {
return <Campo valor={nome} aoMudar={setNome} />
}

SetStateAction<string> é o jeito do React dizer: aceito uma string nova OU uma função que recebe a antiga e devolve a nova

Se o filho só precisa avisar um valor e nunca vai usar a forma de função, dá pra fechar mais o contrato e pedir só (valor: string) => void

Fica mais simples de reaproveitar e o filho não fica amarrado ao estado do pai

E aquele React.FC dos exemplos?

Parada rápida pra tirar uma dúvida que sempre aparece: React.FC é só uma forma de tipar o componente, não tem nada a ver com o useState

Dá pra escrever o mesmo componente como função normal e tipar as props direto no parâmetro:

interface Props {
titulo: string
}

function MeuComponente({ titulo }: Props) {

return <h1>{titulo}: {contador}</h1>
}

Os dois caminhos funcionam e a tipagem do useState é exatamente a mesma nos dois

Escolha o estilo que o seu projeto já usa e siga ele, o problema não é qual dos dois, é misturar os dois no mesmo código

Perguntas frequentes sobre tipar o useState:

Preciso declarar o tipo em todo useState?

Não, quando o valor inicial já revela o tipo (um número, uma string, um booleano) o TypeScript infere sozinho

O generic entra quando o valor inicial não conta a história toda

Como tipar useState que começa nulo?

Usando união com o tipo esperado: useState<Usuario | null>(null)

Depois é só checar antes de acessar as propriedades

Como tipar um array vazio no useState?

Sem isso, o array vazio não aceita os itens que você tentar adicionar depois

Qual é o tipo da função setState pra passar por props?

Dispatch<SetStateAction<Tipo>>, ambos importados do react

Se o filho não precisa da forma de função, (valor: Tipo) => void já resolve

Dá pra usar useState com TypeScript sem criar interface?

Dá, você pode escrever o formato direto no generic ou usar um type

A interface só deixa mais fácil de reaproveitar o mesmo formato em vários lugares

Leia também

Precisa mesmo do <number>? Entendendo a inferência:

Antes de sair tipando tudo na mão, se liga numa coisa: o TypeScript olha o valor inicial e deduz o tipo sozinho

// contador é number, setContador só aceita number

Aqui o <number> seria opcional, o 0 já entrega o recado

Então quando vale a pena escrever o generic?

Quando o valor inicial NÃO conta a história toda:

  • quando o estado começa vazio e depois recebe outra coisa
  • quando o estado pode ter mais de um tipo (string ou null, por exemplo)
  • quando o valor inicial é genérico demais, tipo um array vazio ou um objeto vazio

É a mesma ideia de declarar o retorno de uma função mesmo dando pra inferir: você escreve pra travar a intenção, não pra ensinar o compilador 🙂

Atualizando só uma propriedade do objeto sem quebrar a tipagem:

O exemplo do usuário ali em cima funciona, mas na vida real você quase sempre mexe em um campo por vez

E aqui mora uma pegadinha: o useState não faz merge automático como o setState das classes, o que você passar SUBSTITUI o estado inteiro

setUsuario((anterior) => ({ ...anterior, idade: 31 }))

Esse espalhamento do estado anterior é o que mantém o objeto completo e o tipo intacto

Se você mandar só { idade: 31 }, o TypeScript avisa que está faltando nome

E isso é exatamente o tipo de erro que ele existe pra pegar antes de você descobrir em produção 😀

Como tipar um array no useState:

Esse é o caso que mais pega gente de surpresa

Parece inofensivo, mas o array vazio não tem nenhuma pista de tipo dentro dele

Aí quando você tenta adicionar um item, o TypeScript reclama, porque ele entendeu aquilo como um array que nunca vai receber nada

A correção é dizer o que mora dentro do array:

interface Tarefa {
id: number
titulo: string
feita: boolean
}

function adicionar(nova: Tarefa) {
}

Tome cuidado com array vazio sem generic, é daquelas coisas que só dão as caras lá na frente

Tipando um estado que vem de uma requisição:

Aqui é onde as três coisas que vimos se juntam: objeto, null e generic

O estado começa vazio porque o dado ainda não chegou, e depois ele vira um objeto completo

interface Usuario {
nome: string
idade: number
}

function Perfil() {

useEffect(() => {
buscarUsuario()
.then((dados) => setUsuario(dados))
.finally(() => setCarregando(false))

if (carregando) return <p>Carregando...</p>
if (!usuario) return <p>Nada por aqui</p>

return <p>{usuario.nome} tem {usuario.idade} anos</p>
}

Sacou a jogada? Depois daqueles dois if, o TypeScript já sabe que usuario não é mais nulo, e você acessa usuario.nome sem reclamação nenhuma

Isso chama estreitamento de tipo, e é o motivo real de usar Tipo | null em vez de sair inventando objeto vazio só pra calar o compilador

Como passar o setState para um componente filho:

Cedo ou tarde você vai querer mandar a função de atualizar pra outro componente

E aí bate a dúvida: qual é o tipo dessa função?

O React já exporta ele pronto:

import React, { useState, Dispatch, SetStateAction } from 'react'

interface CampoProps {
valor: string
aoMudar: Dispatch<SetStateAction<string>>
}

function Campo({ valor, aoMudar }: CampoProps) {
return <input value={valor} onChange={(e) => aoMudar(e.target.value)} />
}

function Formulario() {
return <Campo valor={nome} aoMudar={setNome} />
}

SetStateAction<string> é o jeito do React dizer: aceito uma string nova OU uma função que recebe a antiga e devolve a nova

Se o filho só precisa avisar um valor e nunca vai usar a forma de função, dá pra fechar mais o contrato e pedir só (valor: string) => void

Fica mais simples de reaproveitar e o filho não fica amarrado ao estado do pai

E aquele React.FC dos exemplos?

Parada rápida pra tirar uma dúvida que sempre aparece: React.FC é só uma forma de tipar o componente, não tem nada a ver com o useState

Dá pra escrever o mesmo componente como função normal e tipar as props direto no parâmetro:

interface Props {
titulo: string
}

function MeuComponente({ titulo }: Props) {

return <h1>{titulo}: {contador}</h1>
}

Os dois caminhos funcionam e a tipagem do useState é exatamente a mesma nos dois

Escolha o estilo que o seu projeto já usa e siga ele, o problema não é qual dos dois, é misturar os dois no mesmo código

Perguntas frequentes sobre tipar o useState:

Preciso declarar o tipo em todo useState?

Não, quando o valor inicial já revela o tipo (um número, uma string, um booleano) o TypeScript infere sozinho

O generic entra quando o valor inicial não conta a história toda

Como tipar useState que começa nulo?

Usando união com o tipo esperado: useState<Usuario | null>(null)

Depois é só checar antes de acessar as propriedades

Como tipar um array vazio no useState?

Sem isso, o array vazio não aceita os itens que você tentar adicionar depois

Qual é o tipo da função setState pra passar por props?

Dispatch<SetStateAction<Tipo>>, ambos importados do react

Se o filho não precisa da forma de função, (valor: Tipo) => void já resolve

Dá pra usar useState com TypeScript sem criar interface?

Dá, você pode escrever o formato direto no generic ou usar um type

A interface só deixa mais fácil de reaproveitar o mesmo formato em vários lugares

Leia também

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Formações

Formação Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min

Blog | Mais populares