ReactJS: Resumo Prático

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 flexbox
  • grid: ativa CSS Grid
  • grid-cols-{n}: define número de colunas
  • gap-{n}: espaçamento entre itens
  • justify-*: alinha no eixo principal
  • items-*: alinha no eixo cruzado

Tamanho e espaçamento

  • w-{n}, h-{n}: largura e altura
  • p-{n}, m-{n}: padding e margin
  • max-w-{n}: largura máxima
  • min-h-{n}: altura mínima

Tipografia

  • text-{size}: tamanho da fonte
  • font-{weight}: peso da fonte
  • text-{color}: cor do texto
  • leading-{n}: altura da linha

Estados e efeitos

  • hover:{class}: aplica no hover
  • focus:{class}: aplica no foco
  • active:{class}: aplica quando ativo
  • transition: transições suaves
  • shadow, shadow-lg: sombras
  • cursor-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) => (
  • bandeira

    {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-testid
  • queryByText: retorna null se não encontrar (útil para asserções negativas)
  • toHaveLength(n): verifica tamanho de array
  • toBeInTheDocument(): elemento existe no DOM
  • toHaveTextContent('texto'): elemento contém o texto
  • toHaveAttribute('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.

Dúvidas ou feedback? Me encontra no LinkedIn ou GitHub.

Deixe um comentário