Entendendo estados derivados no React

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:

  1. 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	 
  1. 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	 
  1. 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	 
  1. 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:

  1. 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	 
  1. 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	 
  1. 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

  1. 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
  2. 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

  1. Mantenha simples:

    • Comece sem memoização
    • Adicione otimização apenas quando necessário
    • Use ferramentas de profiling para identificar gargalos
  2. 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
  3. 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

  1. 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	 
  2. 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