Fumadocs + Code Hike
Frontend

React

Biblioteca JavaScript para construção de interfaces de usuário com componentes reutilizáveis

O que é React?

React é uma biblioteca JavaScript desenvolvida pelo Facebook para construção de interfaces de usuário. Baseada em componentes reutilizáveis, oferece uma abordagem declarativa para o desenvolvimento de UIs interativas com gerenciamento de estado eficiente.

Por que utilizamos React na IngenioLab?

  • Componentes reutilizáveis: Arquitetura modular e escalável
  • Virtual DOM: Performance otimizada para atualizações de UI
  • Ecossistema rico: Vasta gama de bibliotecas e ferramentas
  • TypeScript friendly: Excelente suporte a tipagem estática
  • Comunidade ativa: Grande suporte e recursos disponíveis
  • Flexibilidade: Pode ser usado em projetos pequenos ou grandes

Instalação

Com Vite (Recomendado):

# Criar novo projeto React + TypeScript
npm create vite@latest meu-projeto -- --template react-ts
cd meu-projeto
npm install
npm run dev

Dependências essenciais:

# React core
npm install react react-dom
# TypeScript support
npm install -D @types/react @types/react-dom typescript
# Development tools
npm install -D @vitejs/plugin-react eslint-plugin-react

Conceitos Fundamentais

1. Componentes

Componentes Funcionais (Padrão IngenioLab):

// src/components/UserCard.tsx
import { FC } from 'react'
interface UserCardProps {
id: string
name: string
email: string
isActive?: boolean
onEdit?: (id: string) => void
}
export const UserCard: FC<UserCardProps> = ({
id,
name,
email,
isActive = true,
onEdit
}) => {
return (
<div className="user-card">
<h3>{name}</h3>
<p>{email}</p>
<span className={`status ${isActive ? 'active' : 'inactive'}`}>
{isActive ? 'Ativo' : 'Inativo'}
</span>
{onEdit && (
<button onClick={() => onEdit(id)}>
Editar
</button>
)}
</div>
)
}

Componentes com Children:

// src/components/Card.tsx
import { FC, ReactNode } from 'react'
interface CardProps {
children: ReactNode
title?: string
className?: string
}
export const Card: FC<CardProps> = ({ children, title, className = '' }) => {
return (
<div className={`card ${className}`}>
{title && <h2 className="card-title">{title}</h2>}
<div className="card-content">
{children}
</div>
</div>
)
}
// Uso
<Card title="Informações do Usuário">
<UserCard id="1" name="João" email="joao@exemplo.com" />
</Card>

2. State (useState)

Estado Local:

// src/components/Counter.tsx
import { useState, FC } from 'react'
export const Counter: FC = () => {
const [count, setCount] = useState<number>(0)
const [step, setStep] = useState<number>(1)
const increment = () => setCount(prev => prev + step)
const decrement = () => setCount(prev => prev - step)
const reset = () => setCount(0)
return (
<div className="counter">
<h2>Contador: {count}</h2>
<div className="controls">
<label>
Passo:
<input
type="number"
value={step}
onChange={(e) => setStep(Number(e.target.value))}
min="1"
/>
</label>
<button onClick={decrement}>-{step}</button>
<button onClick={reset}>Reset</button>
<button onClick={increment}>+{step}</button>
</div>
</div>
)
}

Estado Complexo:

// src/components/UserForm.tsx
import { useState, FC } from 'react'
interface User {
name: string
email: string
age: number
preferences: {
theme: 'light' | 'dark'
notifications: boolean
language: 'pt' | 'en'
}
}
export const UserForm: FC = () => {
const [user, setUser] = useState<User>({
name: '',
email: '',
age: 18,
preferences: {
theme: 'light',
notifications: true,
language: 'pt'
}
})
// Atualizar campo simples
const updateField = <K extends keyof User>(
field: K,
value: User[K]
) => {
setUser(prev => ({ ...prev, [field]: value }))
}
// Atualizar preferências
const updatePreference = <K extends keyof User['preferences']>(
key: K,
value: User['preferences'][K]
) => {
setUser(prev => ({
...prev,
preferences: {
...prev.preferences,
[key]: value
}
}))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
console.log('Dados do usuário:', user)
}
return (
<form onSubmit={handleSubmit} className="user-form">
<input
type="text"
placeholder="Nome"
value={user.name}
onChange={(e) => updateField('name', e.target.value)}
/>
<input
type="email"
placeholder="Email"
value={user.email}
onChange={(e) => updateField('email', e.target.value)}
/>
<input
type="number"
placeholder="Idade"
value={user.age}
onChange={(e) => updateField('age', Number(e.target.value))}
/>
<select
value={user.preferences.theme}
onChange={(e) => updatePreference('theme', e.target.value as 'light' | 'dark')}
>
<option value="light">Claro</option>
<option value="dark">Escuro</option>
</select>
<label>
<input
type="checkbox"
checked={user.preferences.notifications}
onChange={(e) => updatePreference('notifications', e.target.checked)}
/>
Receber notificações
</label>
<button type="submit">Salvar</button>
</form>
)
}

3. Effects (useEffect)

Efeitos básicos:

// src/components/UserProfile.tsx
import { useState, useEffect, FC } from 'react'
interface User {
id: string
name: string
email: string
}
interface UserProfileProps {
userId: string
}
export const UserProfile: FC<UserProfileProps> = ({ userId }) => {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<string | null>(null)
// Effect para buscar dados do usuário
useEffect(() => {
const fetchUser = 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()
setUser(userData)
} catch (err) {
setError(err instanceof Error ? err.message : 'Erro desconhecido')
} finally {
setLoading(false)
}
}
fetchUser()
}, [userId]) // Dependência: re-executa quando userId mudar
// Effect para cleanup (opcional)
useEffect(() => {
return () => {
// Cleanup quando componente for desmontado
console.log('UserProfile desmontado')
}
}, [])
if (loading) return <div>Carregando...</div>
if (error) return <div>Erro: {error}</div>
if (!user) return <div>Usuário não encontrado</div>
return (
<div className="user-profile">
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)
}

Effects avançados:

// src/hooks/useTimer.ts
import { useState, useEffect } from 'react'
export const useTimer = (initialTime: number = 0, interval: number = 1000) => {
const [time, setTime] = useState(initialTime)
const [isRunning, setIsRunning] = useState(false)
useEffect(() => {
let intervalId: NodeJS.Timeout | null = null
if (isRunning) {
intervalId = setInterval(() => {
setTime(prevTime => prevTime + 1)
}, interval)
} else if (intervalId) {
clearInterval(intervalId)
}
// Cleanup function
return () => {
if (intervalId) {
clearInterval(intervalId)
}
}
}, [isRunning, interval])
const start = () => setIsRunning(true)
const pause = () => setIsRunning(false)
const reset = () => {
setTime(initialTime)
setIsRunning(false)
}
return {
time,
isRunning,
start,
pause,
reset
}
}
// Uso do hook
export const TimerComponent: FC = () => {
const { time, isRunning, start, pause, reset } = useTimer(0, 1000)
return (
<div>
<h2>Timer: {time}s</h2>
<button onClick={start} disabled={isRunning}>Start</button>
<button onClick={pause} disabled={!isRunning}>Pause</button>
<button onClick={reset}>Reset</button>
</div>
)
}

4. Context API

Criando Context:

// src/contexts/AuthContext.tsx
import { createContext, useContext, useState, FC, ReactNode } from 'react'
interface User {
id: string
name: string
email: string
role: 'admin' | 'user'
}
interface AuthContextData {
user: User | null
isAuthenticated: boolean
login: (email: string, password: string) => Promise<void>
logout: () => void
loading: boolean
}
const AuthContext = createContext<AuthContextData | undefined>(undefined)
interface AuthProviderProps {
children: ReactNode
}
export const AuthProvider: FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState<boolean>(false)
const login = async (email: string, password: string) => {
try {
setLoading(true)
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
if (!response.ok) {
throw new Error('Credenciais inválidas')
}
const { user, token } = await response.json()
// Salvar token no localStorage
localStorage.setItem('token', token)
setUser(user)
} catch (error) {
throw error
} finally {
setLoading(false)
}
}
const logout = () => {
localStorage.removeItem('token')
setUser(null)
}
const value = {
user,
isAuthenticated: !!user,
login,
logout,
loading
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
// Hook customizado para usar o context
export const useAuth = () => {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth deve ser usado dentro de AuthProvider')
}
return context
}

Usando Context:

// src/components/LoginForm.tsx
import { useState, FC } from 'react'
import { useAuth } from '../contexts/AuthContext'
export const LoginForm: FC = () => {
const { login, loading } = useAuth()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
try {
setError(null)
await login(email, password)
} catch (err) {
setError(err instanceof Error ? err.message : 'Erro no login')
}
}
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
/>
<input
type="password"
placeholder="Senha"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
/>
<button type="submit" disabled={loading}>
{loading ? 'Entrando...' : 'Entrar'}
</button>
</form>
)
}
// src/components/UserInfo.tsx
export const UserInfo: FC = () => {
const { user, logout, isAuthenticated } = useAuth()
if (!isAuthenticated) {
return <div>Não logado</div>
}
return (
<div className="user-info">
<h3>Bem-vindo, {user?.name}!</h3>
<p>Email: {user?.email}</p>
<p>Role: {user?.role}</p>
<button onClick={logout}>Sair</button>
</div>
)
}

Hooks Customizados

1. Hook para Fetch de dados:

// src/hooks/useFetch.ts
import { useState, useEffect } from 'react'
interface UseFetchOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
headers?: Record<string, string>
body?: any
}
interface UseFetchReturn<T> {
data: T | null
loading: boolean
error: string | null
refetch: () => void
}
export function useFetch<T>(
url: string,
options: UseFetchOptions = {}
): UseFetchReturn<T> {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<string | null>(null)
const fetchData = async () => {
try {
setLoading(true)
setError(null)
const response = await fetch(url, {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
...options.headers
},
body: options.body ? JSON.stringify(options.body) : undefined
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
setData(result)
} catch (err) {
setError(err instanceof Error ? err.message : 'Erro desconhecido')
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchData()
}, [url])
return {
data,
loading,
error,
refetch: fetchData
}
}
// Uso do hook
export const UserList: FC = () => {
const { data: users, loading, error, refetch } = useFetch<User[]>('/api/users')
if (loading) return <div>Carregando usuários...</div>
if (error) return <div>Erro: {error}</div>
return (
<div>
<button onClick={refetch}>Recarregar</button>
{users?.map(user => (
<UserCard key={user.id} {...user} />
))}
</div>
)
}

2. Hook para LocalStorage:

// src/hooks/useLocalStorage.ts
import { useState, useEffect } from 'react'
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
// Ler valor do localStorage na inicialização
const [storedValue, setStoredValue] = useState<T>(() => {
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 atualizar o valor
const setValue = (value: T | ((val: T) => T)) => {
try {
// Permitir value ser uma função como no useState
const valueToStore = value instanceof Function ? value(storedValue) : value
// Salvar no state
setStoredValue(valueToStore)
// Salvar no localStorage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error)
}
}
return [storedValue, setValue]
}
// Uso do hook
export const ThemeToggle: FC = () => {
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light')
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light')
}
useEffect(() => {
document.body.className = theme
}, [theme])
return (
<button onClick={toggleTheme}>
Tema atual: {theme}
</button>
)
}

Estrutura de Projeto IngenioLab

src/
├── components/ # Componentes reutilizáveis
│ ├── ui/ # Componentes base (Button, Input, etc)
│ ├── forms/ # Componentes de formulário
│ └── layout/ # Componentes de layout
├── contexts/ # React Contexts
│ ├── AuthContext.tsx
│ └── ThemeContext.tsx
├── hooks/ # Hooks customizados
│ ├── useAuth.ts
│ ├── useFetch.ts
│ └── useLocalStorage.ts
├── pages/ # Páginas da aplicação
├── services/ # Serviços de API
├── types/ # Tipos TypeScript
├── utils/ # Funções utilitárias
└── App.tsx # Componente principal

Padrões de Performance

1. Memoização:

import { memo, useMemo, useCallback, FC } from 'react'
interface ExpensiveComponentProps {
data: any[]
onItemClick: (id: string) => void
}
// Memoizar componente para evitar re-renders desnecessários
export const ExpensiveComponent: FC<ExpensiveComponentProps> = memo(({
data,
onItemClick
}) => {
// Memoizar cálculos pesados
const processedData = useMemo(() => {
return data.map(item => ({
...item,
calculated: item.value * 1.1 // Cálculo pesado
}))
}, [data])
// Memoizar funções para evitar re-renders em children
const handleClick = useCallback((id: string) => {
console.log('Item clicked:', id)
onItemClick(id)
}, [onItemClick])
return (
<div>
{processedData.map(item => (
<ItemComponent
key={item.id}
item={item}
onClick={handleClick}
/>
))}
</div>
)
})

2. Lazy Loading:

import { lazy, Suspense, FC } from 'react'
// Lazy load de componentes
const HeavyComponent = lazy(() => import('./HeavyComponent'))
const UserDashboard = lazy(() => import('./UserDashboard'))
export const App: FC = () => {
return (
<div>
<Suspense fallback={<div>Carregando...</div>}>
<HeavyComponent />
</Suspense>
<Suspense fallback={<div>Carregando dashboard...</div>}>
<UserDashboard />
</Suspense>
</div>
)
}

Testes com React

// src/components/__tests__/UserCard.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { UserCard } from '../UserCard'
describe('UserCard', () => {
const mockUser = {
id: '1',
name: 'João Silva',
email: 'joao@exemplo.com'
}
it('should render user information', () => {
render(<UserCard {...mockUser} />)
expect(screen.getByText('João Silva')).toBeInTheDocument()
expect(screen.getByText('joao@exemplo.com')).toBeInTheDocument()
})
it('should call onEdit when edit button is clicked', () => {
const mockOnEdit = vi.fn()
render(<UserCard {...mockUser} onEdit={mockOnEdit} />)
fireEvent.click(screen.getByText('Editar'))
expect(mockOnEdit).toHaveBeenCalledWith('1')
})
})