React.js Moderno
Domina a lib JavaScript mais querida do planeta. Componentes funcionais, hooks modernos e apps reativas que escalam. Simples assim.
⚛️Por que React.js domina 2026
Mercado de Trabalho
- +180.000 vagas para React no LinkedIn
- Salário médio: R$ 8.000 - R$ 15.000
- Usado por 87% das empresas tech
- Crescimento de 23% ao ano em demanda
Vantagens Técnicas
- Virtual DOM para performance otimizada
- Componentes reutilizáveis
- Ecossistema rico e maduro
- Backed by Meta (Facebook)
1Fundamentos do React
1.1 Setup e Primeira Aplicação
Monta seu ambiente React com Create React App ou Vite. Entende a estrutura do projeto na prática.
# Criando projeto com Create React App
npx create-react-app minha-app-react
cd minha-app-react
npm start
# Ou com Vite (mais rápido)
npm create vite@latest minha-app-react -- --template react
cd minha-app-react
npm install
npm run dev💡 Dica: Vite é bem mais rápido que Create React App. Especialmente pra dev.
1.2 JSX - JavaScript XML
JSX deixa você escrever HTML no JavaScript. É isso que faz React ser intuitivo e potente.
function App() {
const nome = "João";
const idade = 25;
const isLogado = true;
return (
<div className="container">
<h1>Olá, {nome}!</h1>
<p>Você tem {idade} anos</p>
{/* Renderização condicional */}
{isLogado ? (
<p>Bem-vindo de volta!</p>
) : (
<p>Por favor, faça login</p>
)}
{/* Lista */}
<ul>
{['React', 'Vue', 'Angular'].map(framework => (
<li key={framework}>{framework}</li>
))}
</ul>
</div>
);
}⚠️ Importante: JSX precisa ter um elemento pai único ou usar React.Fragment (<></>).
1.3 Componentes Funcionais
Componentes são blocos de código reutilizáveis. Com hooks, os funcionais viraram padrão no lugar das classes.
function Botao({ texto, cor = "blue", onClick }) {
return (
<button
className={`btn btn-${cor}`}
onClick={onClick}
>
{texto}
</button>
);
}
// Uso do componente
function App() {
const handleClick = () => {
alert("Botão clicado!");
};
return (
<div>
<Botao
texto="Clique aqui"
cor="green"
onClick={handleClick}
/>
<Botao texto="Cancelar" cor="red" />
</div>
);
}1.4 Props e PropTypes
Props passam dados entre componentes. PropTypes valida os tipos pra evitar erros.
import PropTypes from 'prop-types';
function PerfilUsuario({ nome, email, avatar, isOnline }) {
return (
<div className="perfil">
<div className="avatar-container">
<img src={avatar} alt={nome} />
<span className={`status ${isOnline ? 'online' : 'offline'}`}></span>
</div>
<div className="info">
<h3>{nome}</h3>
<p>{email}</p>
<span>{isOnline ? 'Online' : 'Offline'}</span>
</div>
</div>
);
}
// Validação de tipos
PerfilUsuario.propTypes = {
nome: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
avatar: PropTypes.string.isRequired,
isOnline: PropTypes.bool
};
// Valores padrão
PerfilUsuario.defaultProps = {
isOnline: false
};2Hooks - A Revolução do React
2.1 useState - Gerenciando Estado Local
useState adiciona estado local em componentes funcionais. O hook mais básico que você vai usar.
import { useState } from 'react';
function Contador() {
const [count, setCount] = useState(0);
const [nome, setNome] = useState('');
const [usuario, setUsuario] = useState({
name: '',
email: '',
age: 0
});
// Incrementar contador
const incrementar = () => {
setCount(count + 1);
// Ou usando função callback (recomendado para atualizações baseadas no estado anterior)
// setCount(prevCount => prevCount + 1);
};
// Atualizar objeto
const atualizarUsuario = (campo, valor) => {
setUsuario(prevUsuario => ({
...prevUsuario,
[campo]: valor
}));
};
return (
<div>
<h2>Contador: {count}</h2>
<button onClick={incrementar}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>Reset</button>
<input
type="text"
value={nome}
onChange={(e) => setNome(e.target.value)}
placeholder="Digite seu nome"
/>
<p>Nome: {nome}</p>
</div>
);
}🎯 Best Practice: Use função callback no setState quando o novo estado depende do anterior: setCount(prev => prev + 1)
2.2 useEffect - Efeitos Colaterais
useEffect cuida dos efeitos colaterais: API calls, timers, subscriptions, DOM. Tudo que acontece "fora" do render.
import { useState, useEffect } from 'react';
function UsuarioInfo({ userId }) {
const [usuario, setUsuario] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Effect que roda quando userId muda
useEffect(() => {
const buscarUsuario = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Usuário não encontrado');
const userData = await response.json();
setUsuario(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (userId) {
buscarUsuario();
}
}, [userId]); // Dependency array - só roda quando userId muda
// Effect para timer (cleanup necessário)
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer executando...');
}, 1000);
// Cleanup function
return () => {
clearInterval(timer);
};
}, []); // Array vazio = roda apenas uma vez (mount/unmount)
// Effect para atualizar título da página
useEffect(() => {
document.title = usuario ? `Perfil de ${usuario.name}` : 'Carregando...';
}, [usuario]);
if (loading) return <div>Carregando...</div>;
if (error) return <div>Erro: {error}</div>;
if (!usuario) return <div>Usuário não encontrado</div>;
return (
<div>
<h2>{usuario.name}</h2>
<p>{usuario.email}</p>
</div>
);
}🚨 Cuidado: Sempre limpe subscriptions, timers e event listeners no cleanup function para evitar memory leaks.
2.3 useContext - Estado Global
useContext compartilha dados entre componentes sem ficar passando props pra todo lado.
import { createContext, useContext, useState } from 'react';
// Criar contexto
const AuthContext = createContext();
// Provider customizado
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const login = async (email, password) => {
setIsLoading(true);
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const userData = await response.json();
setUser(userData);
localStorage.setItem('token', userData.token);
} catch (error) {
console.error('Erro no login:', error);
} finally {
setIsLoading(false);
}
};
const logout = () => {
setUser(null);
localStorage.removeItem('token');
};
const value = {
user,
isLoading,
login,
logout,
isAuthenticated: !!user
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// Hook customizado para usar o contexto
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth deve ser usado dentro de AuthProvider');
}
return context;
}import { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { login, isLoading } = useAuth();
const handleSubmit = async (e) => {
e.preventDefault();
await login(email, password);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Senha"
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Entrando...' : 'Entrar'}
</button>
</form>
);
}2.4 useReducer - Estado Complexo
useReducer é top pra gerenciar estado complexo com várias ações. Tipo um mini Redux.
import { useReducer, useCallback } from 'react';
// Actions
const ACTIONS = {
ADD_TODO: 'ADD_TODO',
TOGGLE_TODO: 'TOGGLE_TODO',
DELETE_TODO: 'DELETE_TODO',
SET_FILTER: 'SET_FILTER'
};
// Reducer
function todosReducer(state, action) {
switch (action.type) {
case ACTIONS.ADD_TODO:
return {
...state,
todos: [
...state.todos,
{
id: Date.now(),
text: action.payload,
completed: false
}
]
};
case ACTIONS.TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case ACTIONS.DELETE_TODO:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case ACTIONS.SET_FILTER:
return {
...state,
filter: action.payload
};
default:
return state;
}
}
// Hook customizado
export function useTodos() {
const [state, dispatch] = useReducer(todosReducer, {
todos: [],
filter: 'all' // 'all', 'active', 'completed'
});
const addTodo = useCallback((text) => {
dispatch({ type: ACTIONS.ADD_TODO, payload: text });
}, []);
const toggleTodo = useCallback((id) => {
dispatch({ type: ACTIONS.TOGGLE_TODO, payload: id });
}, []);
const deleteTodo = useCallback((id) => {
dispatch({ type: ACTIONS.DELETE_TODO, payload: id });
}, []);
const setFilter = useCallback((filter) => {
dispatch({ type: ACTIONS.SET_FILTER, payload: filter });
}, []);
// Computed values
const filteredTodos = state.todos.filter(todo => {
if (state.filter === 'active') return !todo.completed;
if (state.filter === 'completed') return todo.completed;
return true;
});
return {
todos: filteredTodos,
filter: state.filter,
addTodo,
toggleTodo,
deleteTodo,
setFilter
};
}3Hooks Avançados de Performance
3.1 useMemo - Memoização de Valores
useMemo otimiza performance cachando cálculos pesados. Só recalcula quando precisa.
import { useState, useMemo } from 'react';
function ListaProdutos({ produtos }) {
const [filtro, setFiltro] = useState('');
const [ordenacao, setOrdenacao] = useState('nome');
// Cálculo custoso memoizado
const produtosFiltrados = useMemo(() => {
console.log('Filtrando produtos...'); // Só executa quando necessário
return produtos
.filter(produto =>
produto.nome.toLowerCase().includes(filtro.toLowerCase())
)
.sort((a, b) => {
if (ordenacao === 'preco') {
return a.preco - b.preco;
}
return a.nome.localeCompare(b.nome);
});
}, [produtos, filtro, ordenacao]); // Só recalcula se essas deps mudarem
// Estatísticas memoizadas
const estatisticas = useMemo(() => {
const total = produtosFiltrados.length;
const precoMedio = produtosFiltrados.reduce((acc, p) => acc + p.preco, 0) / total;
const produtoMaisCaro = produtosFiltrados.reduce((max, p) => p.preco > max.preco ? p : max);
return { total, precoMedio, produtoMaisCaro };
}, [produtosFiltrados]);
return (
<div>
<div className="filtros">
<input
type="text"
value={filtro}
onChange={(e) => setFiltro(e.target.value)}
placeholder="Filtrar produtos..."
/>
<select
value={ordenacao}
onChange={(e) => setOrdenacao(e.target.value)}
>
<option value="nome">Ordenar por Nome</option>
<option value="preco">Ordenar por Preço</option>
</select>
</div>
<div className="estatisticas">
<p>Total: {estatisticas.total} produtos</p>
<p>Preço médio: R$ {estatisticas.precoMedio.toFixed(2)}</p>
<p>Mais caro: {estatisticas.produtoMaisCaro?.nome}</p>
</div>
<div className="produtos">
{produtosFiltrados.map(produto => (
<div key={produto.id} className="produto">
<h3>{produto.nome}</h3>
<p>R$ {produto.preco}</p>
</div>
))}
</div>
</div>
);
}3.2 useCallback - Memoização de Funções
useCallback evita re-renders quando você passa funções como props. Memoriza a função.
import { useState, useCallback, memo } from 'react';
// Componente filho memoizado
const TodoItem = memo(({ todo, onToggle, onDelete }) => {
console.log('Renderizando TodoItem:', todo.id);
return (
<div className="todo-item">
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span className={todo.completed ? 'completed' : ''}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>Deletar</button>
</div>
);
});
function TodoApp() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
// Sem useCallback - nova função a cada render
// const toggleTodo = (id) => {
// setTodos(prev => prev.map(todo =>
// todo.id === id ? { ...todo, completed: !todo.completed } : todo
// ));
// };
// Com useCallback - função memoizada
const toggleTodo = useCallback((id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, []); // Nenhuma dependência externa
const deleteTodo = useCallback((id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
const addTodo = useCallback(() => {
if (newTodo.trim()) {
setTodos(prev => [...prev, {
id: Date.now(),
text: newTodo,
completed: false
}]);
setNewTodo('');
}
}, [newTodo]); // Depende de newTodo
return (
<div>
<div>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Nova tarefa..."
/>
<button onClick={addTodo}>Adicionar</button>
</div>
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo} // Função memoizada
onDelete={deleteTodo} // Função memoizada
/>
))}
</div>
</div>
);
}⚡ Performance: Use useCallback + React.memo para evitar re-renders custosos em listas grandes.
3.3 Custom Hooks - Reutilização de Lógica
Custom Hooks deixam você extrair e reutilizar lógica com estado. Cria seus próprios hooks.
import { useState, useEffect, useCallback } from 'react';
export function useApi(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = useCallback(async (customUrl = url, customOptions = {}) => {
try {
setLoading(true);
setError(null);
const response = await fetch(customUrl, {
...options,
...customOptions
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
return result;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [url, options]);
const mutate = useCallback((newData) => {
setData(newData);
}, []);
const reset = useCallback(() => {
setData(null);
setError(null);
setLoading(false);
}, []);
useEffect(() => {
if (url) {
fetchData();
}
}, [fetchData, url]);
return {
data,
loading,
error,
refetch: fetchData,
mutate,
reset
};
}import { useState, useEffect } from 'react';
export function useLocalStorage(key, initialValue) {
// Estado para armazenar valor
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":, error`);
return initialValue;
}
});
// Função para setar valor
const setValue = (value) => {
try {
// Permite que value seja uma função como useState
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
console.error(`Error setting localStorage key "${key}":, error`);
}
};
// Remover item do localStorage
const removeValue = () => {
try {
setStoredValue(initialValue);
if (typeof window !== 'undefined') {
window.localStorage.removeItem(key);
}
} catch (error) {
console.error(`Error removing localStorage key "${key}":, error`);
}
};
return [storedValue, setValue, removeValue];
}import { useApi } from '../hooks/useApi';
import { useLocalStorage } from '../hooks/useLocalStorage';
function PerfilUsuario() {
// Hook de API
const { data: usuario, loading, error, refetch } = useApi('/api/user/profile');
// Hook de localStorage
const [configuracoes, setConfiguracoes] = useLocalStorage('userSettings', {
tema: 'light',
idioma: 'pt-BR',
notificacoes: true
});
const atualizarTema = (novoTema) => {
setConfiguracoes(prev => ({ ...prev, tema: novoTema }));
};
if (loading) return <div>Carregando perfil...</div>;
if (error) return <div>Erro: {error}</div>;
return (
<div className={configuracoes.tema}>
<h1>Perfil de {usuario?.nome}</h1>
<p>Email: {usuario?.email}</p>
<div>
<h3>Configurações</h3>
<label>
Tema:
<select
value={configuracoes.tema}
onChange={(e) => atualizarTema(e.target.value)}
>
<option value="light">Claro</option>
<option value="dark">Escuro</option>
</select>
</label>
</div>
<button onClick={refetch}>Atualizar Perfil</button>
</div>
);
}🚀Projeto Prático: App de Tarefas Completo
Vamos construir um aplicativo completo de gerenciamento de tarefas utilizando todos os conceitos aprendidos.
Funcionalidades
- CRUD completo de tarefas
- Filtros (todas, ativas, concluídas)
- Persistência no localStorage
- Contadores e estatísticas
- Interface responsiva
Conceitos Aplicados
- useState e useReducer
- useEffect para side effects
- useContext para estado global
- useMemo e useCallback
- Custom Hooks
📋 Estrutura do Projeto
📚Próximos Passos no Aprendizado
React Avançado
- React Router para navegação
- Formulários com Formik ou React Hook Form
- Gerenciamento de estado com Redux Toolkit
- Testes com Jest e React Testing Library
- Performance e otimização
Frameworks React
- Next.js para aplicações full-stack
- Gatsby para sites estáticos
- React Native para mobile
- Electron para desktop
- Server Components e Suspense
🛠️Ferramentas de Desenvolvimento
Dev Tools
- • React Developer Tools
- • Redux DevTools
- • React Hook Form DevTools
- • Why Did You Render
- • React Profiler
UI Libraries
- • Material-UI (MUI)
- • Ant Design
- • Chakra UI
- • React Bootstrap
- • Tailwind CSS + Headless UI
Build Tools
- • Vite (recomendado)
- • Create React App
- • Webpack
- • Parcel
- • ESBuild
Domine React.js e Transforme sua Carreira
Com essa base sólida de React.js, você está pronto pra construir apps modernas e se destacar no mercado de frontend.
Continue Aprendendo
React.js é só o começo. Continua a jornada com Next.js, TypeScript, testes e muito mais.