Entendendo estados derivados no React
Estado derivado no React representa valores calculados a partir de state ou props existentes. Embora seja simples em conceito, entender quando e como usar um estado derivado de forma eficaz pode melhorar significativamente a performance da aplicação e a manutenibilidade do código.
O que é um estado derivado?
Estado derivado refere-se a valores que podem ser calculados inteiramente a partir de outras partes do state ou props. Em vez de armazenar esses valores calculáveis no state, eles são computados quando necessário.
Quando usar um estado derivado:
- Filtragem ou ordenação de listas:
1 function ProductList({ products }) {
2 // Estado derivado - produtos filtrados por disponibilidade
3 const availableProducts = products.filter(product => product.inStock);
4
5 // Estado derivado - ordenados por preço
6 const sortedProducts = [...availableProducts].sort((a, b) => a.price - b.price);
7
8 return (
9 <ul>
10 {sortedProducts.map(product => (
11 <li key={product.id}>{product.name} - ${product.price}</li>
12 ))}
13 </ul>
14 );
15 }
16 - Calculando totais ou médias:
1 function ShoppingCart({ items }) {
2 // Estado derivado - preço total
3 const totalPrice = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
4
5 // Estado derivado - preço médio por item
6 const averagePrice = items.length > 0 ? totalPrice / items.length : 0;
7
8 return (
9 <div>
10 <p>Total: ${totalPrice}</p>
11 <p>Preço médio por item: ${averagePrice.toFixed(2)}</p>
12 </div>
13 );
14 }
15 - Formatando dados para exibição:
1 function UserProfile({ user }) {
2 // Estado derivado - nome formatado
3 const displayName = `${user.title} ${user.firstName} ${user.lastName}`;
4
5 // Estado derivado - endereço formatado
6 const fullAddress = `${user.street}, ${user.city}, ${user.country} ${user.zipCode}`;
7
8 return (
9 <div>
10 <h2>{displayName}</h2>
11 <p>{fullAddress}</p>
12 </div>
13 );
14 }
15 - Combinando múltiplos valores de state:
1 function SearchResults({ users, searchTerm, filterRole }) {
2 // Estado derivado - combina critérios de busca e filtro
3 const filteredUsers = users.filter(user => {
4 const matchesSearch = user.name.toLowerCase().includes(searchTerm.toLowerCase());
5 const matchesRole = filterRole === 'all' || user.role === filterRole;
6 return matchesSearch && matchesRole;
7 });
8
9 return (
10 <ul>
11 {filteredUsers.map(user => (
12 <li key={user.id}>{user.name} - {user.role}</li>
13 ))}
14 </ul>
15 );
16 }
17 Anti-patterns a evitar:
- Armazenando valores calculáveis no state:
1 function ProductList({ products }) {
2 // ❌ Má prática
3 const [totalPrice, setTotalPrice] = useState(0);
4
5 useEffect(() => {
6 const sum = products.reduce((acc, product) => acc + product.price, 0);
7 setTotalPrice(sum);
8 }, [products]);
9
10 // ✅ Melhor abordagem
11 const totalPrice = products.reduce((acc, product) => acc + product.price, 0);
12
13 return <div>Total: ${totalPrice}</div>;
14 }
15 - Duplicando props no state:
1 function UserCard({ user }) {
2 // ❌ Má prática
3 const [userName, setUserName] = useState(user.name);
4
5 useEffect(() => {
6 setUserName(user.name);
7 }, [user.name]);
8
9 // ✅ Melhor abordagem - usar a prop diretamente
10 return <div>{user.name}</div>;
11 }
12 - Atualizando valores derivados em useEffect:
1 function FilteredList({ items, searchQuery }) {
2 // ❌ Má prática
3 const [filteredItems, setFilteredItems] = useState([]);
4
5 useEffect(() => {
6 const filtered = items.filter(item =>
7 item.name.toLowerCase().includes(searchQuery.toLowerCase())
8 );
9 setFilteredItems(filtered);
10 }, [items, searchQuery]);
11
12 // ✅ Melhor abordagem - computar durante o render
13 const filteredItems = items.filter(item =>
14 item.name.toLowerCase().includes(searchQuery.toLowerCase())
15 );
16
17 return (
18 <ul>
19 {filteredItems.map(item => (
20 <li key={item.id}>{item.name}</li>
21 ))}
22 </ul>
23 );
24 }
25 Padrões de implementação
A forma mais simples de estado derivado é um cálculo direto dentro do método render:
1 function ProductList({ products }) {
2 const totalPrice = products.reduce((sum, product) => sum + product.price, 0);
3
4 return (
5 <div>
6 <p>Total: ${totalPrice}</p>
7 <ul>{/* Renderização da lista de produtos */}</ul>
8 </div>
9 );
10 }
11 Quando os cálculos são custosos, use useMemo para memorizar resultados:
1 function FilteredList({ items, filterCriteria }) {
2 const filteredItems = useMemo(() => {
3 return items.filter(item => {
4 return item.category === filterCriteria;
5 });
6 }, [items, filterCriteria]);
7
8 return (
9 <ul>
10 {filteredItems.map(item => (
11 <li key={item.id}>{item.name}</li>
12 ))}
13 </ul>
14 );
15 }
16 Para gerenciamento complexo de state, considere usar padrões de selector:
1 function UserDashboard({ user }) {
2 // Função selector
3 const getFullName = useCallback(() => {
4 return `${user.firstName} ${user.lastName}`;
5 }, [user.firstName, user.lastName]);
6
7 return <h1>Bem-vindo, {getFullName()}</h1>;
8 }
9 Considerações de performance
-
Trade-offs de memoização:
- Memoize apenas cálculos custosos
- Considere o custo de memória da memoização
- Avalie cuidadosamente o conteúdo do array de dependências
-
Timing de computação:
- Prefira computar valores durante o render
- Evite atualizações desnecessárias do state
- Considere usar web workers para computações pesadas
Melhores práticas
-
Mantenha simples:
- Comece sem memoização
- Adicione otimização apenas quando necessário
- Use ferramentas de profiling para identificar gargalos
-
Mantenha funções puras:
- Garanta que os cálculos sejam determinísticos
- Evite side effects em computações de estados derivados
- Mantenha os cálculos autocontidos
-
Documente dependências:
- Documentação clara dos requisitos de entrada
- Arrays de dependência explícitos em hooks
- Comentários explicando cálculos complexos
Erros comuns
-
Otimização excessiva:
1 // Memoização desnecessária 2 const capitalizedName = useMemo(() => { 3 return name.toUpperCase(); 4 }, [name]); 5 6 // Melhor abordagem 7 const capitalizedName = name.toUpperCase(); 8 -
Dependências incorretas:
1 // Dependência faltando 2 const fullName = useMemo(() => { 3 return `${firstName} ${lastName}`; 4 }, [firstName]); // lastName faltando 5 6 // Implementação correta 7 const fullName = useMemo(() => { 8 return `${firstName} ${lastName}`; 9 }, [firstName, lastName]); 10
Testando um estado derivado
1 describe('ProductList', () => {
2 it('calcula corretamente o preço total', () => {
3 const products = [
4 { id: 1, price: 10 },
5 { id: 2, price: 20 }
6 ];
7
8 const { getByText } = render(<ProductList products={products} />);
9 expect(getByText('Total: $30')).toBeInTheDocument();
10 });
11 });
12 Conclusão
Estado derivado é um conceito poderoso no React quando usado apropriadamente. Seguindo esses padrões e melhores práticas, desenvolvedores podem criar aplicações mais sustentáveis e performáticas. Lembre-se de medir os impactos na performance antes de adicionar complexidade através de memoização ou outras técnicas de otimização.
Recursos adicionais
- Documentação React: Hooks API Reference
- React DevTools: Profiler
- React DevTools: Extension