Props e state no React

1. Fundamentos das Props

Props (propriedades) são o mecanismo fundamental para passar dados entre componentes React. Elas funcionam como parâmetros de função, mas com uma característica crucial: são imutáveis. Um componente nunca deve modificar suas próprias props — isso garante o fluxo unidirecional de dados e previsibilidade no comportamento da aplicação.

// Componente pai passando props
function App() {
  const usuario = { nome: "Maria", idade: 28 };
  return <Saudacao nome={usuario.nome} idade={usuario.idade} />;
}

// Componente filho recebendo e desestruturando props
function Saudacao({ nome, idade }) {
  return (
    <div>
      <h1>Olá, {nome}!</h1>
      <p>Você tem {idade} anos.</p>
    </div>
  );
}

Props padrão e validação com PropTypes

Para tornar componentes mais robustos, podemos definir valores padrão e validar tipos das props recebidas:

import PropTypes from 'prop-types';

function Botao({ texto, cor, desabilitado }) {
  return (
    <button 
      style={{ backgroundColor: cor }}
      disabled={desabilitado}
    >
      {texto}
    </button>
  );
}

Botao.defaultProps = {
  texto: "Clique aqui",
  cor: "blue",
  desabilitado: false
};

Botao.propTypes = {
  texto: PropTypes.string,
  cor: PropTypes.string,
  desabilitado: PropTypes.bool,
  onClick: PropTypes.func
};

2. State Local com useState

Enquanto props representam dados imutáveis vindos de fora, state representa dados mutáveis internos ao componente. O hook useState permite adicionar estado a componentes funcionais.

import { useState } from 'react';

function Contador() {
  const [contador, setContador] = useState(0);

  // Usando função setter com valor direto
  const incrementar = () => setContador(contador + 1);

  // Usando função setter com callback (recomendado quando depende do estado anterior)
  const incrementarSeguro = () => setContador(prev => prev + 1);

  return (
    <div>
      <p>Valor: {contador}</p>
      <button onClick={incrementarSeguro}>+</button>
    </div>
  );
}

Exemplo prático: formulário controlado

function Formulario() {
  const [nome, setNome] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Dados enviados:', { nome, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={nome}
        onChange={(e) => setNome(e.target.value)}
        placeholder="Nome"
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <button type="submit">Enviar</button>
    </form>
  );
}

3. Fluxo de Dados: Props para State

Inicializar state a partir de props é comum, mas requer cuidado para evitar o anti-pattern de cópia desnecessária:

// ❌ Anti-pattern: copiar props para state sem necessidade
function Item({ nomeInicial }) {
  const [nome, setNome] = useState(nomeInicial); // Sincronização problemática
  // Se o pai atualizar nomeInicial, o filho não será atualizado
}

// ✅ Correto: usar props diretamente ou com chave para reset
function Item({ nome }) {
  return <span>{nome}</span>; // Props já são reativas
}

Atualizando estado do filho via callback do pai

function ListaTarefas() {
  const [tarefas, setTarefas] = useState([]);

  const adicionarTarefa = (novaTarefa) => {
    setTarefas(prev => [...prev, { id: Date.now(), texto: novaTarefa }]);
  };

  const removerTarefa = (id) => {
    setTarefas(prev => prev.filter(t => t.id !== id));
  };

  return (
    <div>
      <FormularioTarefa onAdicionar={adicionarTarefa} />
      <Lista tarefas={tarefas} onRemover={removerTarefa} />
    </div>
  );
}

function FormularioTarefa({ onAdicionar }) {
  const [texto, setTexto] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (texto.trim()) {
      onAdicionar(texto);
      setTexto('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={texto} onChange={(e) => setTexto(e.target.value)} />
      <button type="submit">Adicionar</button>
    </form>
  );
}

function Lista({ tarefas, onRemover }) {
  return (
    <ul>
      {tarefas.map(tarefa => (
        <li key={tarefa.id}>
          {tarefa.texto}
          <button onClick={() => onRemover(tarefa.id)}>Remover</button>
        </li>
      ))}
    </ul>
  );
}

4. State vs. Props: Quando Usar Cada Um

Característica Props State
Origem Componente pai Componente atual
Mutabilidade Imutável Mutável via setter
Finalidade Configuração externa Dados internos mutáveis
Atualização Re-renderização do pai Chamada explícita ao setter

Exemplo comparativo: Componente com props vs. state

// Componente com props apenas (apresentacional)
function ExibirContador({ valor }) {
  return <p>Contagem: {valor}</p>;
}

// Componente com state (interativo)
function ContadorInterativo() {
  const [valor, setValor] = useState(0);
  return (
    <div>
      <p>Contagem: {valor}</p>
      <button onClick={() => setValor(v => v + 1)}>Incrementar</button>
    </div>
  );
}

5. Imutabilidade e Atualizações Seguras

O React usa shallow comparison para detectar mudanças. Modificar objetos/arrays diretamente impede que o React perceba a alteração.

// ❌ Mutação direta (NÃO FAÇA)
const [usuario, setUsuario] = useState({ nome: 'João', enderecos: [] });
usuario.nome = 'Maria'; // Não dispara re-renderização
setUsuario(usuario); // React não detecta mudança

// ✅ Criando nova referência (CORRETO)
const atualizarNome = (novoNome) => {
  setUsuario(prev => ({
    ...prev,
    nome: novoNome
  }));
};

// Atualizando arrays de forma segura
const [itens, setItens] = useState([1, 2, 3]);

// Adicionar
setItens(prev => [...prev, 4]);

// Remover
setItens(prev => prev.filter(item => item !== 2));

// Atualizar
setItens(prev => prev.map(item => 
  item === 2 ? 99 : item
));

6. Boas Práticas e Padrões Comuns

Organização de props e state derivado

import { useMemo } from 'react';

function DashboardUsuarios({ usuarios }) {
  const [filtro, setFiltro] = useState('');

  // State derivado com useMemo para performance
  const usuariosFiltrados = useMemo(() => {
    return usuarios.filter(u => 
      u.nome.toLowerCase().includes(filtro.toLowerCase())
    );
  }, [usuarios, filtro]);

  const estatisticas = useMemo(() => ({
    total: usuarios.length,
    ativos: usuarios.filter(u => u.ativo).length,
    mediaIdade: usuarios.reduce((acc, u) => acc + u.idade, 0) / usuarios.length
  }), [usuarios]);

  return (
    <div>
      <input
        type="text"
        value={filtro}
        onChange={(e) => setFiltro(e.target.value)}
        placeholder="Filtrar usuários"
      />

      <div className="estatisticas">
        <p>Total: {estatisticas.total}</p>
        <p>Ativos: {estatisticas.ativos}</p>
        <p>Média idade: {estatisticas.mediaIdade.toFixed(1)}</p>
      </div>

      <ul>
        {usuariosFiltrados.map(usuario => (
          <li key={usuario.id}>{usuario.nome} - {usuario.idade} anos</li>
        ))}
      </ul>
    </div>
  );
}

Referências