React: Noções básicas sobre estado: useState

Última atualização: 2026-08-26

O State é a memória de um componente. Se os Props são parâmetros fornecidos por terceiros (somente leitura), então o State é o seu próprio caderninho (editável). O useState é o “chip de memória” que o React instala em um componente.


1. O que você vai aprender



2. Reflexões suscitadas por um contador

(1) Problema: as variáveis regulares não acionam atualizações na interface do usuário

Bob quer criar um contador simples: quando você clica no botão, o número aumenta em 1.

JSX
// ❌ Ordinary Variables,Will not trigger a re-render
function Counter() {
  let count = 0  // Ordinary Variables

  function handleClick() {
    count = count + 1  // Variable changed, but UI won't update!
    console.log(count) // Console Display 1, 2, 3... But the page always displays 0
  }

  return (
    <div>
      <p>Count:{count}</p>  {/* Always Show 0 */}
      <button onClick={handleClick}>+1</button>
    </div>
  )
}
▶ Experimente

Bob descobriu um problema: As variáveis comuns não avisam o React para atualizar a interface do usuário. Toda vez que o componente é renderizado novamente, count é redefinido para 0.

(2) Uma solução utilizando useState

JSX
// ✅ Usage State,React It automatically tracks changes and updates UI
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)  // Initial value 0

  function handleClick() {
    setCount(count + 1)  // Update Status → React Automatic Re-rendering
  }

  return (
    <div>
      <p>Count:{count}</p>  {/* Automatically update with each click */}
      <button onClick={handleClick}>+1</button>
    </div>
  )
}
▶ Experimente

Valor de retorno: useState retorna um array [current value, setter function]. Após chamar setCount, o React re-renderiza automaticamente o componente para exibir os valores mais recentes.



3. Conceitos fundamentais do useState

(1) Análise da tarefa

JSX
import { useState } from 'react'

// useState(initialValue) Back [value, setValue]
const [count, setCount] = useState(0)
//      ^      ^          ^
//      |      |          └─ Initial value(Applies only to the first render)
//      |      └─ Update Function(Triggers a re-render after being called)
//      └─ Current status value(Get the latest value on every render)
▶ Experimente
Seção Descrição
count Valor atual do estado; o valor mais recente é recuperado a cada renderização
setCount Função de atualização: ao chamá-la → o React refaz a renderização do componente
useState(0) Valor inicial (usado durante a primeira renderização; ignorado nas renderizações subsequentes)

(2) Mecanismo de atualização

JSX
function Example() {
  const [count, setCount] = useState(0)

  function handleClick() {
    setCount(count + 1)  // 1. Request Update
    // Note:Here count Still the old value!
    console.log(count)    // Output 0,No 1
  }

  return <button onClick={handleClick}>{count}</button>
}
▶ Experimente

Conceito-chave: setCount(count + 1) significa “solicitar ao React que atualize count na próxima renderização”, e não “alterar count imediatamente”. Isso é chamado de atualização assíncrona.

(3) Atualizações funcionais

Se o novo estado depender do antigo, você deve usar atualizações funcionais:

JSX
function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    // ✅ Recommendations:Functional Updates
    setCount(prev => prev + 1)
    setCount(prev => prev + 1)  // Called twice in a row, count becomes +2
  }

  // ❌ Not recommended:Update Directly(Chain calls take effect only once)
  function handleBadClick() {
    setCount(count + 1)
    setCount(count + 1)  // Called twice count All are old values,The result was only +1
  }

  return <button onClick={handleClick}>Currently:{count}</button>
}
▶ Experimente
Método Sintaxe Características
Atualizar diretamente setCount(count + 1) Simples, mas os dados são perdidos em chamadas consecutivas
Atualização funcional setCount(prev => prev + 1) Preciso; funciona corretamente mesmo com chamadas consecutivas


4. Atualização de matrizes e objetos (imutabilidade)

O React exige que o estado não seja modificado diretamente. Nunca use state.push() ou state.name = 'xxx'; em vez disso, crie um novo array ou objeto para substituir o valor antigo.

(1) Objetos a serem atualizados

JSX
function UserEditor() {
  const [user, setUser] = useState({
    name: 'Alice',
    age: 28,
    email: 'alice@example.com'
  })

  function updateName(newName) {
    // ❌ Error:Edit directly state Object
    user.name = newName
    // React Changes will not be detected,UI Will not be updated

    // ✅ Correct:Create a new object
    setUser({ ...user, name: newName })
  }

  function updateAge(newAge) {
    // ✅ Expand Operator:Keep the other fields,Update Only age
    setUser({ ...user, age: newAge })
  }

  function resetUser() {
    // ✅ Reset to default values
    setUser({ name: '', age: 0, email: '' })
  }

  return (
    <div>
      <p>{user.name} - {user.age} years old</p>
      <button onClick={() => updateName('Bob')}>Change Name Bob</button>
      <button onClick={() => updateAge(user.age + 1)}>Age +1</button>
    </div>
  )
}
▶ Experimente

(2) Atualizar o array

JSX
function ShoppingCart() {
  const [items, setItems] = useState([
    { id: 1, name: 'Apple', qty: 2 },
    { id: 2, name: 'Banana', qty: 1 }
  ])

  // Add:Create a new array using the spread operator
  function addItem(name) {
    setItems([...items, { id: Date.now(), name, qty: 1 }])
  }

  // Delete:use  filter Create a new array
  function removeItem(id) {
    setItems(items.filter(item => item.id !== id))
  }

  // Update:use  map Create a new array
  function updateQty(id, newQty) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: newQty } : item
    ))
  }

  return (
    <div>
      <button onClick={() => addItem('Orange')}>Add orange</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name} × {item.qty}
            <button onClick={() => updateQty(item.id, item.qty + 1)}>+</button>
            <button onClick={() => removeItem(item.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  )
}
▶ Experimente

▶ Exemplo: Tabela de referência rápida para operações com matrizes

JSX
// ============================================
// Example:React Array Operations in C 6 Common Scenarios
// ============================================

const [arr, setArr] = useState([1, 2, 3])

// 1. Append to the end
setArr([...arr, 4])           // [1, 2, 3, 4]

// 2. Add to the beginning
setArr([0, ...arr])           // [0, 1, 2, 3]

// 3. Insert in the middle
const insertAt = 1
setArr([...arr.slice(0, insertAt), 99, ...arr.slice(insertAt)])
                              // [1, 99, 2, 3]

// 4. Delete Element(filter)
setArr(arr.filter(n => n !== 2))  // [1, 3]

// 5. Update Element(map)
setArr(arr.map(n => n === 2 ? 22 : n))  // [1, 22, 3]

// 6. Sort(Copy first, then sort)
setArr([...arr].sort((a, b) => b - a))  // [3, 2, 1]
▶ Experimente

5. Várias variáveis de estado

Um componente pode ter várias variáveis de estado. Recomenda-se dividi-las por lógica, em vez de agrupá-las em um único objeto grande:

JSX
function RegistrationForm() {
  // ✅ Recommendations:Logical Breakdown
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)
  const [errors, setErrors] = useState({})

  // ❌ Not recommended:All states are stored in a single object
  const [form, setForm] = useState({
    name: '', email: '', password: '',
    isSubmitting: false, errors: {}
  })
  // Expand the entire object when updating,Fields that are easy to overlook
}
▶ Experimente
Estratégia Vantagens Desvantagens
Dividir em vários useStates Atualizações precisas, seguras em termos de tipos e fáceis de ler Muitas variáveis quando há muito estado
Combinado em um único useState Prático para atualizar vários campos de uma só vez A sintaxe de atualização é complicada e é fácil esquecer de expandi-la

Recomendação: Use várias instâncias de useState para estados independentes (como campos de entrada, indicadores de carregamento e mensagens de erro); campos logicamente relacionados (como todos os campos de um perfil de usuário) podem ser combinados em um único objeto.



6. Elevando o Estado

Quando vários componentes precisam compartilhar o mesmo estado, transfira o estado para o componente pai comum mais próximo.

100%
graph TB
    subgraph "Before the status upgrade(Data is not shared)"
        A[Parent Component App] --- B[Child component A<br/>Have their own count]
        A --- C[Child component B<br/>Have their own count]
    end
    
    subgraph "After the status upgrade(Data Sharing)"
        D[Parent Component App<br/>**count Here**] --- E[Child component A<br/>Read count, Call setCount]
        D --- F[Child component B<br/>Read count, Call setCount]
    end

▶ Exemplo: Aplicação prática da promoção de estado

JSX 📖 Somente leitura
// ============================================
// Example:Temperature Converter(Status Upgrade)
// Features:Converting Between Celsius and Fahrenheit,Share the same temperature value
// ============================================

// ---- Child component:Celsius Temperature Input ----
function CelsiusInput({ celsius, onCelsiusChange }) {
  return (
    <div>
      <label>Celsius(°C):</label>
      <input
        value={celsius}
        onChange={e => onCelsiusChange(e.target.value)}
        style={{ margin: '8px', padding: '4px' }}
      />
    </div>
  )
}

// ---- Child component:Fahrenheit Temperature Input ----
function FahrenheitInput({ fahrenheit, onFahrenheitChange }) {
  return (
    <div>
      <label>Fahrenheit(°F):</label>
      <input
        value={fahrenheit}
        onChange={e => onFahrenheitChange(e.target.value)}
        style={{ margin: '8px', padding: '4px' }}
      />
    </div>
  )
}

// ---- Parent Component:Status is managed here ----
function TemperatureConverter() {
  // State Propagated to the Common Parent Component
  const [temperature, setTemperature] = useState('')

  function handleCelsiusChange(value) {
    setTemperature(value)  // Store at Celsius
    // There is no need for two states!Keep only one,Another one calculated using a formula
  }

  function handleFahrenheitChange(value) {
    // Fahrenheit → Celsius:°C = (°F - 32) × 5/9
    setTemperature(value ? ((parseFloat(value) - 32) * 5 / 9).toFixed(1) : '')
  }

  const celsius = temperature
  const fahrenheit = temperature
    ? (parseFloat(temperature) * 9 / 5 + 32).toFixed(1)
    : ''

  return (
    <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
      <h2>Temperature Converter</h2>
      <CelsiusInput celsius={celsius} onCelsiusChange={handleCelsiusChange} />
      <FahrenheitInput fahrenheit={fahrenheit} onFahrenheitChange={handleFahrenheitChange} />
      {temperature && (
        <p style={{ color: '#666', marginTop: '12px' }}>
          {celsius}°C = {fahrenheit}°F
        </p>
      )}
    </div>
  )
}
// Enter in the Celsius input field 100 → Automatic Display in Fahrenheit 212°F
// Enter in the Fahrenheit input field 212 → Automatic Celsius Display 100°C
49 linhas de lógica (limite de 40, somente leitura)

7. Exemplo completo: Contador do carrinho de compras

JSX
// ============================================
// Complete Example:Shopping Cart(useState Comprehensive Application)
// Features:Add Item、Increase or Decrease Quantity、Delete、Total Price Calculation
// ============================================

import { useState } from 'react'

function ShoppingCart() {
  // Several State Variable,Logical Breakdown
  const [items, setItems] = useState([
    { id: 1, name: 'React Hands-On Tutorials', price: 89, qty: 1 },
    { id: 2, name: 'TypeScript Getting Started', price: 59, qty: 2 }
  ])
  const [discountCode, setDiscountCode] = useState('')
  const [appliedDiscount, setAppliedDiscount] = useState(0)

  // Increase the quantity(Functional updates ensure accuracy)
  function increment(id) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: item.qty + 1 } : item
    ))
  }

  // Decrease the quantity(No less than 1)
  function decrement(id) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: Math.max(1, item.qty - 1) } : item
    ))
  }

  // Delete Product
  function remove(id) {
    setItems(items.filter(item => item.id !== id))
  }

  // Apply a discount code
  function applyDiscount() {
    if (discountCode === 'REACT2026') {
      setAppliedDiscount(20)  // over 100 minus 20
    } else {
      alert('The discount code is invalid.')
    }
  }

  // Calculate the Total Price
  const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0)
  const total = Math.max(0, subtotal - appliedDiscount)

  return (
    <div style={{ maxWidth: '600px', margin: '0 auto' }}>
      <h2>🛒 Shopping Cart</h2>

      {/* Product List */}
      {items.length === 0 ? (
        <p style={{ color: '#999', textAlign: 'center', padding: '40px' }}>
          Your shopping cart is empty,Go check it out!!
        </p>
      ) : (
        items.map(item => (
          <div key={item.id} style={{
            display: 'flex', alignItems: 'center',
            padding: '12px', borderBottom: '1px solid #f0f0f0'
          }}>
            <div style={{ flex: 1 }}>
              <h4 style={{ margin: 0 }}>{item.name}</h4>
              <p style={{ margin: '4px 0', color: '#ff4d4f' }}>${item.price}</p>
            </div>
            
            {/* Quantity Control */}
            <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              <button onClick={() => decrement(item.id)} style={btnStyle}>−</button>
              <span>{item.qty}</span>
              <button onClick={() => increment(item.id)} style={btnStyle}>+</button>
            </div>

            {/* Subtotal */}
            <p style={{ margin: '0 16px', fontWeight: 'bold', width: '80px', textAlign: 'right' }}>
              ${item.price * item.qty}
            </p>

            {/* Delete */}
            <button onClick={() => remove(item.id)} style={{ ...btnStyle, backgroundColor: '#ff4d4f', color: 'white' }}>
              ✕
            </button>
          </div>
        ))
      )}

      {/* Discount Code */}
      <div style={{ marginTop: '16px', display: 'flex', gap: '8px' }}>
        <input
          value={discountCode}
          onChange={e => setDiscountCode(e.target.value)}
          placeholder="Enter the discount code"
          style={{ flex: 1, padding: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
        />
        <button onClick={applyDiscount} style={{
          padding: '8px 16px', backgroundColor: '#52c41a', color: 'white',
          border: 'none', borderRadius: '4px', cursor: 'pointer'
        }}>
          Applications
        </button>
      </div>

      {/* Price Summary */}
      <div style={{ marginTop: '16px', padding: '16px', backgroundColor: '#fafafa', borderRadius: '8px' }}>
        <p>Subtotal:${subtotal}</p>
        {appliedDiscount > 0 && <p style={{ color: '#52c41a' }}>Discount: -${appliedDiscount}</p>}
        <p style={{ fontSize: '20px', fontWeight: 'bold' }}>Total:${total}</p>
      </div>
    </div>
  )
}

const btnStyle = {
  width: '32px', height: '32px',
  border: '1px solid #d9d9d9', borderRadius: '4px',
  backgroundColor: 'white', cursor: 'pointer',
  fontSize: '16px', display: 'flex', alignItems: 'center',
  justifyContent: 'center'
}

Fluxo de interação:

  • Toque em +/− para ajustar a quantidade; o subtotal e o total são atualizados em tempo real
  • Clique em ✕ para excluir um item; exiba uma mensagem amigável quando o carrinho de compras estiver vazio
  • Digite o código promocional REACT2026 para economizar 20 yuan

▶ Exemplo 3: Atualizando o estado de um objeto — Editando informações do usuário

JSX
function UserEditor() {
  const [user, setUser] = useState({ name: 'Alice', email: 'alice@test.com', age: 28 })

  function updateField(field, value) {
    setUser(prev => ({ ...prev, [field]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Edit Profile</h3>
      <input value={user.name} onChange={e => updateField('name', e.target.value)}
        placeholder="Name" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input value={user.email} onChange={e => updateField('email', e.target.value)}
        placeholder="Email" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input type="number" value={user.age} onChange={e => updateField('age', Number(e.target.value))}
        placeholder="Age" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, fontSize: 13 }}>
        {JSON.stringify(user, null, 2)}
      </pre>
    </div>
  )
}
▶ Experimente

▶ Exemplo 4: Estados de objetos aninhados — Gerenciamento de endereços

JSX
function AddressForm() {
  const [address, setAddress] = useState({
    street: '', city: '', zip: '',
    country: 'US', isPrimary: true,
  })

  function update(path, value) {
    setAddress(prev => ({ ...prev, [path]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Shipping Address</h3>
      <input value={address.street} onChange={e => update('street', e.target.value)}
        placeholder="Street" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
        <input value={address.city} onChange={e => update('city', e.target.value)}
          placeholder="City" style={{ flex: 2, padding: 8, borderRadius: 4 }} />
        <input value={address.zip} onChange={e => update('zip', e.target.value)}
          placeholder="ZIP" style={{ flex: 1, padding: 8, borderRadius: 4 }} />
      </div>
      <select value={address.country} onChange={e => update('country', e.target.value)}
        style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }}>
        <option value="US">United States</option>
        <option value="CN">China</option>
        <option value="JP">Japan</option>
      </select>
      <label>
        <input type="checkbox" checked={address.isPrimary}
          onChange={e => update('isPrimary', e.target.checked)} /> Primary address
      </label>
    </div>
  )
}
▶ Experimente

▶ Exemplo 5: As atualizações funcionais resolvem o problema das atualizações em lote

JSX
function ScoreBoard() {
  const [score, setScore] = useState(0)
  const [multiplier, setMultiplier] = useState(1)

  function addPoints(base) {
    setScore(prev => prev + base * multiplier)
  }

  function resetScore() {
    setScore(0)
    setMultiplier(1)
  }

  function doubleMultiplier() {
    setMultiplier(prev => Math.min(prev * 2, 8))
  }

  return (
    <div style={{ maxWidth: 300, margin: '0 auto', textAlign: 'center' }}>
      <h3>Score: {score}</h3>
      <p>Multiplier: x{multiplier}</p>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginBottom: 8 }}>
        <button onClick={() => addPoints(10)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+10 pts</button>
        <button onClick={() => addPoints(50)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+50 pts</button>
        <button onClick={() => addPoints(100)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+100 pts</button>
      </div>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
        <button onClick={doubleMultiplier} style={{ padding: '8px 16px', cursor: 'pointer' }}>2x Multiplier</button>
        <button onClick={resetScore} style={{ padding: '8px 16px', cursor: 'pointer' }}>Reset</button>
      </div>
    </div>
  )
}
▶ Experimente

❓ Perguntas Frequentes

P: Por que count ainda exibe o valor antigo quando impresso imediatamente após a chamada de setCount? R: Porque setCount é uma “solicitação de atualização”, e não uma “atualização imediata”. O React atualiza o estado em lotes depois que todos os manipuladores de eventos são executados e, em seguida, aciona uma nova renderização. O novo valor de count só estará disponível na próxima renderização. Isso é chamado de atualização assíncrona.

P: O que acontece se eu modificar diretamente o objeto de estado (user.name = 'xxx')? R: Nenhum erro será gerado, mas a interface do usuário não será atualizada. O React determina se deve re-renderizar comparando as referências aos estados antigo e novo. Modificar diretamente as propriedades de um objeto não altera a referência; portanto, o React considera-o “inalterado”. Portanto, você deve criar um novo objeto: setUser({...user, name: 'xxx'}).

P: Qual é a diferença de desempenho entre useState e as variáveis comuns? R: useState acarreta uma sobrecarga adicional (rastreamento de alterações, agendamento de renderização), portanto, só deve ser usada em cenários em que “as atualizações da interface do usuário são acionadas quando ocorrem alterações”. Se uma variável for alterada, mas não exigir uma atualização da interface do usuário (como um ID de temporizador ou a posição de rolagem), é mais adequado usar useRef (Lição 10).

P: Quantos useStates podem existir em um único componente? Há algum problema de desempenho? R: Não há limite para o número. A documentação oficial do React recomenda dividir os elementos em vários useStates com base na lógica, em vez de usar um único objeto grande. Cada chamada de useState é uma “unidade de estado”, e o React consegue lidar com eficiência com um grande número de useStates. Componentes comuns geralmente têm de 3 a 8 useStates.

P: A atualização do setState é síncrona ou assíncrona? R: Antes do React 18, o setState era “agrupado automaticamente” dentro de manipuladores de eventos, mas era síncrono nas chamadas do setTimeout e do Promise. O React 18 introduziu o Agrupamento Automático, de modo que setState agora é agrupado de forma assíncrona, independentemente de ser chamado dentro de um manipulador de eventos, setTimeout, Promise ou callback de fetch. Se você precisar absolutamente acessar o DOM atualizado de forma síncrona, pode usar flushSync(() => setState(...)).


📖 Resumo


📝 Exercícios

  1. Exercício básico (Dificuldade ⭐): Crie um componente LikeButton que alterne entre os estados ❤️ e 🤍 quando clicado e exiba o número de curtidas.
  2. Exercício avançado (Dificuldade ⭐⭐): Crie um componente ExpenseTracker que utilize useState para gerenciar uma lista de despesas (descrição, valor, data) e permita adicionar e remover itens.
  3. Desafio (Dificuldade: ⭐⭐⭐): Crie um componente ColorPicker que utilize três instâncias de useState para gerenciar os valores dos controles deslizantes R, G e B (0–255) e exiba uma pré-visualização da cor em tempo real e o código hexadecimal da cor.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%