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
- Documentação oficial do React: Components and Props — Guia completo sobre props, imutabilidade e composição de componentes
- Documentação oficial do React: State and Lifecycle — Explicação detalhada sobre estado, ciclo de vida e boas práticas
- React Hooks: useState — Tutorial oficial sobre o hook useState com exemplos práticos
- PropTypes no React — Documentação sobre validação de tipos com PropTypes e valores padrão
- Lifting State Up no React — Padrão para compartilhar estado entre componentes movendo-o para o ancestral comum
- Imutabilidade no React: por que é importante — Artigo técnico explicando shallow comparison, performance e boas práticas de imutabilidade
- useMemo e useCallback: guia prático — Blog de Kent C. Dodds sobre memoização e otimização de performance no React