Pular para o conteúdo
Curso Completo

React.js Moderno

Domina a lib JavaScript mais querida do planeta. Componentes funcionais, hooks modernos e apps reativas que escalam. Simples assim.

Componentes Funcionais
Hooks Modernos
State Management

⚛️
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)

1
Fundamentos 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.

terminal
# 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.

App.jsx
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.

components/Botao.jsx
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.

components/PerfilUsuario.jsx
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
};

2
Hooks - 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.

components/Contador.jsx
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.

components/UsuarioInfo.jsx
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.

contexts/AuthContext.jsx
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;
}
components/LoginForm.jsx
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.

hooks/useTodos.jsx
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
  };
}

3
Hooks Avançados de Performance

3.1 useMemo - Memoização de Valores

useMemo otimiza performance cachando cálculos pesados. Só recalcula quando precisa.

components/ListaProdutos.jsx
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.

components/TodoApp.jsx
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.

hooks/useApi.jsx
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
  };
}
hooks/useLocalStorage.jsx
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];
}
components/UsuandoCustomHooks.jsx
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

src/
├── components/
│ ├── TodoList.jsx
│ ├── TodoItem.jsx
│ ├── AddTodo.jsx
│ └── FilterButtons.jsx
├── hooks/
│ ├── useTodos.js
│ └── useLocalStorage.js
├── contexts/
│ └── TodoContext.jsx
└── App.jsx

📚
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.

Fundamentos sólidos estabelecidos
Hooks modernos dominados
Performance otimizada

Continue Aprendendo

React.js é só o começo. Continua a jornada com Next.js, TypeScript, testes e muito mais.

Próximo: Next.js Fundamentals90% concluído