Gerenciando Estado Complexo
Módulo 3: Estado e Ciclo de Vida
Aula 3
1
Estado com Objetos e Arrays
Aprenda a gerenciar estado com estruturas de dados complexas
Estado com Objetos e Arrays
Princípio da Imutabilidade
No React, o estado deve ser tratado como imutável. Sempre crie uma nova cópia ao atualizar.
Trabalhando com Objetos
Atualizando Propriedades
function PerfilUsuario() {
const [usuario, setUsuario] = useState({
nome: 'João',
email: 'joao@email.com',
idade: 25,
endereco: {
cidade: 'São Paulo',
estado: 'SP'
}
});
// Atualizar uma propriedade
const atualizarNome = (novoNome) => {
setUsuario(prevUsuario => ({
...prevUsuario,
nome: novoNome
}));
};
// Atualizar propriedade aninhada
const atualizarCidade = (novaCidade) => {
setUsuario(prevUsuario => ({
...prevUsuario,
endereco: {
...prevUsuario.endereco,
cidade: novaCidade
}
}));
};
return (
<div>
<h2>{usuario.nome}</h2>
<p>{usuario.email}</p>
<p>{usuario.endereco.cidade}, {usuario.endereco.estado}</p>
</div>
);
}
Trabalhando com Arrays
Operações Comuns
function ListaTarefas() {
const [tarefas, setTarefas] = useState([
{ id: 1, texto: 'Estudar React', concluida: false },
{ id: 2, texto: 'Fazer exercícios', concluida: true }
]);
// Adicionar item
const adicionarTarefa = (texto) => {
const novaTarefa = {
id: Date.now(),
texto,
concluida: false
};
setTarefas(prevTarefas => [...prevTarefas, novaTarefa]);
};
// Remover item
const removerTarefa = (id) => {
setTarefas(prevTarefas =>
prevTarefas.filter(tarefa => tarefa.id !== id)
);
};
// Atualizar item
const toggleTarefa = (id) => {
setTarefas(prevTarefas =>
prevTarefas.map(tarefa =>
tarefa.id === id
? { ...tarefa, concluida: !tarefa.concluida }
: tarefa
)
);
};
// Ordenar array
const ordenarPorStatus = () => {
setTarefas(prevTarefas =>
[...prevTarefas].sort((a, b) => a.concluida - b.concluida)
);
};
return (
<div>
{tarefas.map(tarefa => (
<div key={tarefa.id}>
<input
type="checkbox"
checked={tarefa.concluida}
onChange={() => toggleTarefa(tarefa.id)}
/>
<span>{tarefa.texto}</span>
<button onClick={() => removerTarefa(tarefa.id)}>X</button>
</div>
))}
</div>
);
}
Estado com Múltiplas Estruturas
function CarrinhoCompras() {
const [carrinho, setCarrinho] = useState({
itens: [],
cupomDesconto: null,
enderecoEntrega: {
rua: '',
cidade: '',
cep: ''
}
});
// Adicionar item ao carrinho
const adicionarItem = (produto) => {
setCarrinho(prevCarrinho => {
const itemExistente = prevCarrinho.itens.find(
item => item.id === produto.id
);
if (itemExistente) {
// Incrementa quantidade se já existe
return {
...prevCarrinho,
itens: prevCarrinho.itens.map(item =>
item.id === produto.id
? { ...item, quantidade: item.quantidade + 1 }
: item
)
};
} else {
// Adiciona novo item
return {
...prevCarrinho,
itens: [...prevCarrinho.itens, { ...produto, quantidade: 1 }]
};
}
});
};
// Aplicar cupom
const aplicarCupom = (cupom) => {
setCarrinho(prevCarrinho => ({
...prevCarrinho,
cupomDesconto: cupom
}));
};
// Atualizar endereço
const atualizarEndereco = (campo, valor) => {
setCarrinho(prevCarrinho => ({
...prevCarrinho,
enderecoEntrega: {
...prevCarrinho.enderecoEntrega,
[campo]: valor
}
}));
};
// Calcular total
const calcularTotal = () => {
const subtotal = carrinho.itens.reduce(
(total, item) => total + (item.preco * item.quantidade),
0
);
if (carrinho.cupomDesconto) {
return subtotal * (1 - carrinho.cupomDesconto.percentual);
}
return subtotal;
};
return (
<div>
{/* Interface do carrinho */}
</div>
);
}
Padrões de Atualização Imutável
1. Spread Operator
// Objeto
const novoObjeto = { ...objetoAntigo, propriedade: novoValor };
// Array
const novoArray = [...arrayAntigo, novoItem];
2. Métodos de Array que Retornam Novo Array
// filter - remove itens
const filtrado = array.filter(item => item.id !== idParaRemover);
// map - atualiza itens
const atualizado = array.map(item =>
item.id === idParaAtualizar ? { ...item, ...novosDados } : item
);
// concat - adiciona itens
const comNovosItens = array.concat(novosItens);
3. Evite Mutações
// ❌ ERRADO - muta o array original
array.push(novoItem);
array[0].nome = 'Novo Nome';
array.sort();
// ✅ CORRETO - cria novo array
setArray([...array, novoItem]);
setArray(array.map((item, i) => i === 0 ? { ...item, nome: 'Novo Nome' } : item));
setArray([...array].sort());
Dicas de Performance
1. Use Funções de Atualização
// Evita problemas de fechamento (closure)
setEstado(prevEstado => {
// Use prevEstado ao invés de estado
return novoEstado;
});
2. Normalize Dados Complexos
// Ao invés de arrays aninhados
const [dados, setDados] = useState([
{ id: 1, nome: 'Item', subitens: [{ id: 2, nome: 'Subitem' }] }
]);
// Considere normalizar
const [itens, setItens] = useState({
1: { id: 1, nome: 'Item' },
2: { id: 2, nome: 'Subitem', parentId: 1 }
});
3. Separe Estados Independentes
// Ao invés de um grande objeto
const [estado, setEstado] = useState({
usuario: {},
carrinho: [],
configuracoes: {}
});
// Prefira estados separados
const [usuario, setUsuario] = useState({});
const [carrinho, setCarrinho] = useState([]);
const [configuracoes, setConfiguracoes] = useState({});
2
3
Projeto: Carrinho de Compras
Implemente um carrinho de compras completo usando estado complexo
Activity
Projeto: Carrinho de Compras
Objetivo
Criar um carrinho de compras funcional que demonstre o gerenciamento de estado complexo.
Funcionalidades Requeridas
-
Lista de Produtos
- Exibir produtos com nome, preço e imagem
- Botão para adicionar ao carrinho
-
Carrinho
- Mostrar itens adicionados
- Quantidade de cada item
- Botões para aumentar/diminuir quantidade
- Botão para remover item
- Calcular subtotal por item
- Calcular total geral
-
Recursos Extras
- Contador de itens no carrinho
- Mensagem quando carrinho está vazio
- Persistência no localStorage
Estrutura de Dados
// Produtos disponíveis
const produtosDisponiveis = [
{
id: 1,
nome: 'Notebook',
preco: 2999.90,
imagem: 'https://via.placeholder.com/200'
},
{
id: 2,
nome: 'Mouse Gamer',
preco: 199.90,
imagem: 'https://via.placeholder.com/200'
},
// ... mais produtos
];
// Estado do carrinho
const carrinhoInicial = {
itens: [],
// itens terá estrutura: { id, nome, preco, quantidade }
};
Template Inicial
import React, { useState, useEffect } from 'react';
function CarrinhoDeCompras() {
const produtosDisponiveis = [
{ id: 1, nome: 'Notebook', preco: 2999.90 },
{ id: 2, nome: 'Mouse Gamer', preco: 199.90 },
{ id: 3, nome: 'Teclado Mecânico', preco: 399.90 },
{ id: 4, nome: 'Monitor 24"', preco: 899.90 },
{ id: 5, nome: 'Headset', preco: 299.90 }
];
// Seu código aqui
return (
<div>
<h1>Loja Virtual</h1>
{/* Implemente a interface aqui */}
</div>
);
}
export default CarrinhoDeCompras;
Dicas de Implementação
1. Adicionar ao Carrinho
const adicionarAoCarrinho = (produto) => {
// Verificar se produto já existe no carrinho
// Se sim, incrementar quantidade
// Se não, adicionar com quantidade 1
};
2. Atualizar Quantidade
const atualizarQuantidade = (id, novaQuantidade) => {
// Se quantidade for 0, remover do carrinho
// Senão, atualizar quantidade
};
3. Calcular Total
const calcularTotal = () => {
return carrinho.itens.reduce((total, item) => {
return total + (item.preco * item.quantidade);
}, 0);
};
Solução Completa
Clique para ver a solução
import React, { useState, useEffect } from 'react';
function CarrinhoDeCompras() {
const produtosDisponiveis = [
{ id: 1, nome: 'Notebook', preco: 2999.90 },
{ id: 2, nome: 'Mouse Gamer', preco: 199.90 },
{ id: 3, nome: 'Teclado Mecânico', preco: 399.90 },
{ id: 4, nome: 'Monitor 24"', preco: 899.90 },
{ id: 5, nome: 'Headset', preco: 299.90 }
];
// Inicializa carrinho do localStorage ou vazio
const [carrinho, setCarrinho] = useState(() => {
const carrinhoSalvo = localStorage.getItem('carrinho');
return carrinhoSalvo ? JSON.parse(carrinhoSalvo) : { itens: [] };
});
// Salva carrinho no localStorage quando muda
useEffect(() => {
localStorage.setItem('carrinho', JSON.stringify(carrinho));
}, [carrinho]);
const adicionarAoCarrinho = (produto) => {
setCarrinho(prevCarrinho => {
const itemExistente = prevCarrinho.itens.find(item => item.id === produto.id);
if (itemExistente) {
return {
...prevCarrinho,
itens: prevCarrinho.itens.map(item =>
item.id === produto.id
? { ...item, quantidade: item.quantidade + 1 }
: item
)
};
} else {
return {
...prevCarrinho,
itens: [...prevCarrinho.itens, { ...produto, quantidade: 1 }]
};
}
});
};
const atualizarQuantidade = (id, novaQuantidade) => {
if (novaQuantidade <= 0) {
removerDoCarrinho(id);
} else {
setCarrinho(prevCarrinho => ({
...prevCarrinho,
itens: prevCarrinho.itens.map(item =>
item.id === id
? { ...item, quantidade: novaQuantidade }
: item
)
}));
}
};
const removerDoCarrinho = (id) => {
setCarrinho(prevCarrinho => ({
...prevCarrinho,
itens: prevCarrinho.itens.filter(item => item.id !== id)
}));
};
const limparCarrinho = () => {
setCarrinho({ itens: [] });
};
const calcularTotal = () => {
return carrinho.itens.reduce((total, item) => {
return total + (item.preco * item.quantidade);
}, 0);
};
const contarItens = () => {
return carrinho.itens.reduce((total, item) => total + item.quantidade, 0);
};
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '20px' }}>
<h1>Loja Virtual</h1>
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '20px' }}>
{/* Lista de Produtos */}
<div>
<h2>Produtos Disponíveis</h2>
<div style={{ display: 'grid', gap: '10px' }}>
{produtosDisponiveis.map(produto => (
<div key={produto.id} style={{
border: '1px solid #ddd',
padding: '10px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<div>
<h3>{produto.nome}</h3>
<p>R$ {produto.preco.toFixed(2)}</p>
</div>
<button onClick={() => adicionarAoCarrinho(produto)}>
Adicionar ao Carrinho
</button>
</div>
))}
</div>
</div>
{/* Carrinho */}
<div style={{
border: '1px solid #ddd',
padding: '20px',
position: 'sticky',
top: '20px',
height: 'fit-content'
}}>
<h2>Carrinho ({contarItens()} itens)</h2>
{carrinho.itens.length === 0 ? (
<p>Seu carrinho está vazio</p>
) : (
<>
{carrinho.itens.map(item => (
<div key={item.id} style={{
borderBottom: '1px solid #eee',
paddingBottom: '10px',
marginBottom: '10px'
}}>
<h4>{item.nome}</h4>
<p>R$ {item.preco.toFixed(2)} cada</p>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<button onClick={() => atualizarQuantidade(item.id, item.quantidade - 1)}>
-
</button>
<span>{item.quantidade}</span>
<button onClick={() => atualizarQuantidade(item.id, item.quantidade + 1)}>
+
</button>
<button onClick={() => removerDoCarrinho(item.id)}>
Remover
</button>
</div>
<p>Subtotal: R$ {(item.preco * item.quantidade).toFixed(2)}</p>
</div>
))}
<div style={{
marginTop: '20px',
paddingTop: '20px',
borderTop: '2px solid #333'
}}>
<h3>Total: R$ {calcularTotal().toFixed(2)}</h3>
<button onClick={limparCarrinho} style={{ marginTop: '10px' }}>
Limpar Carrinho
</button>
</div>
</>
)}
</div>
</div>
</div>
);
}
export default CarrinhoDeCompras;
Desafios Extras
- Sistema de Cupons: Adicione campo para inserir cupom de desconto
- Filtros: Implemente filtros por preço ou categoria
- Busca: Adicione busca por nome de produto
- Wishlist: Crie lista de desejos
- Animações: Adicione transições suaves ao adicionar/remover itens
3 content items