React é um modelo de componentes, não um framework completo. Entender hooks, contexto, o algoritmo de reconciliação e os padrões de gerenciamento de estado é o que determina se você vai construir aplicações mantíveis ou uma sopa de componentes. Este post é uma referência prática para consultar quando estiver começando um projeto novo ou se perdendo na documentação.
TL;DR: Uma referência React cobrindo hooks, padrões de componentes, contexto, performance, roteamento e gerenciamento de estado.
Stack: React, JavaScript/TypeScript
Nível: Intermediário
Tempo de leitura: ~25 min
Criando um projeto com Vite
Esqueça o Create React App. O Vite é mais rápido, mais leve e o padrão moderno para novos projetos React. Esses três comandos te colocam rodando em menos de um minuto.
npm create vite@latest . --template react-ts
npm install
npm run dev
Adicionando Tailwind
Tailwind é um framework CSS utilitário. Em vez de escrever .card { padding: 16px; }, você escreve p-4 direto no elemento. Parece estranho até você tentar, e depois não tem como voltar.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Abra o tailwind.config.js e configure os caminhos de conteúdo:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: { extend: {} },
plugins: [],
}
Abra o src/index.css e substitua tudo por essas três linhas:
@tailwind base;
@tailwind components;
@tailwind utilities;
Design system com variáveis CSS
Antes de ir com tudo no Tailwind, vale definir os tokens base do design: cores, fontes, espaçamentos. Pense nisso como um contrato entre você e a interface. Cada botão e título usa os mesmos valores, então mudar uma cor da marca significa editar uma linha, não cinquenta.
:root {
--primary-color: #007bff;
--secondary-color: #25292d;
--success-color: #28a745;
--error-color: #dc3545;
--background-color: #f8f9fa;
--text-color: #343a40;
--primary-font: 'Roboto', sans-serif;
--spacing-small: 8px;
--spacing-medium: 16px;
--spacing-large: 32px;
}
.primary-button {
background-color: var(--primary-color);
color: white;
padding: var(--spacing-small) var(--spacing-medium);
border: none;
border-radius: 4px;
font-family: var(--primary-font);
}
.heading {
font-family: var(--primary-font);
font-size: 24px;
font-weight: bold;
color: var(--text-color);
margin-bottom: var(--spacing-medium);
}
Classes essenciais do Tailwind
Tailwind tem centenas de utilitários, mas a maioria dos projetos reais usa uns 30 deles. Aqui está uma referência rápida das classes que você vai usar o tempo todo.
Layout
flex,inline-flex: ativa flexboxgrid: ativa CSS Gridgrid-cols-{n}: define número de colunasgap-{n}: espaçamento entre itensjustify-*: alinha no eixo principalitems-*: alinha no eixo cruzado
Tamanho e espaçamento
w-{n},h-{n}: largura e alturap-{n},m-{n}: padding e marginmax-w-{n}: largura máximamin-h-{n}: altura mínima
Tipografia
text-{size}: tamanho da fontefont-{weight}: peso da fontetext-{color}: cor do textoleading-{n}: altura da linha
Estados e efeitos
hover:{class}: aplica no hoverfocus:{class}: aplica no focoactive:{class}: aplica quando ativotransition: transições suavesshadow,shadow-lg: sombrascursor-pointer: cursor de ponteiro
Styled Components
Se Tailwind não for o seu estilo (sem trocadilho), o Styled Components permite escrever CSS de verdade dentro do arquivo do componente. Você tem todo o poder do CSS com escopo por componente. O custo é que os estilos ficam em JavaScript, o que algumas pessoas adoram e outras acham profundamente perturbador.
npm install styled-components
import styled from 'styled-components';
export const Button = styled.button`
background-color: ${(props) => (props.primary ? 'azul' : 'cinza')};
color: white;
font-size: 16px;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
&:hover {
background-color: ${(props) => (props.primary ? 'darkblue' : 'darkgray')};
}`;
Componente que consome uma API
Apps reais buscam dados. Aqui está o padrão completo: instale o axios, defina uma interface TypeScript para o formato da resposta, faça o fetch dentro de um useEffect e renderize. A interface pode parecer muita digitação, mas te poupa de surpresas desagradáveis quando a API muda o formato dos dados.
npm i -D axios dotenv @types/node
Defina a interface e faça o fetch dentro do useEffect:
// CountriesList.tsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
interface Country {
name: { common: string; };
capital: string[];
region: string;
population: number;
flags: { png: string; };
cca2: string;
}
const CountriesList: React.FC = () => {
const [countries, setCountries] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(`${import.meta.env.VITE_BASE_URL}`);
setCountries(response.data);
} catch (error) {
console.error('Erro ao buscar países:', error);
}
};
fetchData();
}, []);
return (
Países
{countries.map((country) => (
-
{country.name.common}
Capital: {country.capital[0]}
Região: {country.region}
População: {country.population}
))}
);
};
export default CountriesList;
Gerenciamento de estado com Redux Toolkit
Redux costumava ser doloroso. O Redux Toolkit eliminou boa parte do boilerplate. O padrão é: crie um slice (estado e reducers em um único arquivo), configure a store, envolva o app com um Provider, depois leia com useSelector e escreva com useDispatch. Pense nisso como um quadro branco compartilhado onde qualquer componente pode ler ou escrever, sem precisar passar props por toda a hierarquia.
npm install @reduxjs/toolkit react-redux
Crie o slice em src/store/cartSlice.ts:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface CartItem { id: number; name: string; price: number; quantity: number; }
interface CartState { items: CartItem[]; }
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] } as CartState,
reducers: {
addItem: (state, action: PayloadAction) => {
const existing = state.items.find(i => i.id === action.payload.id);
if (existing) { existing.quantity += action.payload.quantity; }
else { state.items.push(action.payload); }
},
removeItem: (state, action: PayloadAction) => {
state.items = state.items.filter(i => i.id !== action.payload);
},
},
});
export const { addItem, removeItem } = cartSlice.actions;
export default cartSlice.reducer;
Configure a store e envolva o app com o Provider:
// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './cartSlice';
export const store = configureStore({ reducer: { cart: cartReducer } });
export type RootState = ReturnType;
export type AppDispatch = typeof store.dispatch;
// main.tsx
import { Provider } from 'react-redux';
import { store } from './store';
createRoot(document.getElementById('root')!).render(
);
Testes com TDD e Testing Library
TDD em React significa: escreva o teste primeiro, veja ele falhar, depois escreva o componente mínimo para fazê-lo passar. Parece ao contrário no início, mas te força a pensar na interface antes da implementação. A filosofia do Testing Library é testar comportamento, não internos: se o usuário consegue ver ou clicar, é isso que você testa.
npm install jest @testing-library/react @testing-library/jest-dom @testing-library/user-event @types/jest ts-jest ts-node jest-environment-jsdom
Adicione o jest.config.ts e atualize o tsconfig.json:
// jest.config.ts
export default {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testMatch: ['**/**/*.test.tsx']
};
// tsconfig.json (adicione em compilerOptions)
"types": ["node", "jest", "@testing-library/jest-dom"],
"jsx": "react-jsx"
Escreva o teste primeiro, depois o componente mínimo que o faz passar:
import { render, screen } from '@testing-library/react';
import { CountryList } from './CountryList';
import '@testing-library/jest-dom';
test('CountryList renderiza países', async () => {
render( );
const items = await screen.findAllByRole('listitem');
expect(items).toHaveLength(5);
});
Queries e matchers mais usados
findAllByRole: busca por papel semântico (assíncrono, aguarda o elemento)getByRole: busca por papel semântico (síncrono, lança erro se não encontrar)getByTestId: busca pelo atributo data-testidqueryByText: retorna null se não encontrar (útil para asserções negativas)toHaveLength(n): verifica tamanho de arraytoBeInTheDocument(): elemento existe no DOMtoHaveTextContent('texto'): elemento contém o textotoHaveAttribute('href', '/home'): elemento tem o atributo com o valor
Sempre importe @testing-library/jest-dom nos seus arquivos de teste. Sem isso, os matchers customizados como toBeInTheDocument vão lançar erros confusos.
O que você construiu
Uma configuração React completa: Vite com TypeScript, Tailwind, um design system com variáveis CSS, consumo de API com tipagem correta, Redux Toolkit para estado global e um fluxo TDD com Testing Library. Esses são os blocos de construção de aplicações reais de produção.
Próximos passos
- Aprenda o algoritmo de reconciliação do React: ele explica por que props de chave importam, quando separar componentes e por que re-renders desnecessários acontecem.
- Use o profiler do React DevTools para encontrar gargalos de performance reais antes de sair adicionando useMemo e useCallback em tudo.
- Separe estado de servidor do estado de cliente. O React Query cuida dos dados da API de forma limpa, deixando o useState e o useReducer focados apenas no estado de UI.