Published on

Como estruturo os meus projetos

Authors
  • avatar
    Name
    Maicon Oliveira
    Twitter

Estruturando Projetos

Neste artigo, vou compartilhar como estruturo meus projetos front-end utilizando um stack moderno e bem alinhado com as melhores práticas do mercado. A combinação de React Query para gerenciamento de estado assíncrono, Context API para compartilhamento de estados globais, React Hook Form e Zod para gerenciamento e validação de formulários, ShadCN para componentes acessíveis e personalizados e Tailwind CSS para estilização rápida e consistente.

Vou detalhar como essas bibliotecas se encaixam na estrutura do projeto, mostrando suas integrações e explicando as decisões que tomam parte do fluxo de desenvolvimento. Vamos explorar as vantagens e desvantagens de cada uma e como aproveitá-las ao máximo para criar aplicações front-end eficientes, otimizadas e de fácil manutenção.

Utilizando React Query:

Vantagens

  • Gerenciamento de Cache Automático: React Query mantém um cache de dados e otimiza as chamadas de API, reduzindo a necessidade de requisições desnecessárias.
  • Sincronização Automática: Sincroniza automaticamente os dados entre o servidor e o cliente, garantindo que as informações estejam sempre atualizadas.
  • Revalidação e Refetching: Fácil configuração para revalidar dados após ações como foco da tela, reconexão de rede, ou intervalos definidos.
  • Controle de Erros e Status: Gerencia automaticamente estados de carregamento, erro e sucesso, simplificando o código e a UX.
  • SSR e SSG: Integração perfeita com Next.js para renderização de dados do lado do servidor ou estático.

Fetching de Dados com React Query:

Vamos criar um exemplo simples onde utilizamos o React Query para buscar uma lista de usuários de uma API pública e exibi-los em uma página do Next.js.

  1. Instalação do React Query:

    Primeiro, instale o React Query e o React Query Devtools para ajudar no desenvolvimento:

    npm install @tanstack/react-query
    npm install @tanstack/react-query-devtools
    
  2. Configuração do QueryClients:

    Para usar o React Query, é necessário configurar o QueryClient na raiz da aplicação. Crie um arquivo src/pages/_app.tsx e configure o QueryClientProvider:

    // src/pages/_app.tsx
    import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
    import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
    import type { AppProps } from 'next/app'
    import '../styles/globals.css'
    
    const queryClient = new QueryClient()
    
    function MyApp({ Component, pageProps }: AppProps) {
      return (
        <QueryClientProvider client={queryClient}>
          <Component {...pageProps} />
          <ReactQueryDevtools initialIsOpen={false} />
        </QueryClientProvider>
      )
    }
    
    export default MyApp
    
  3. Criando um Hook para Buscar Dados:

    Crie um custom hook que usa o React Query para buscar dados de uma API. Aqui, utilizamos o endpoint público da API de usuários https://jsonplaceholder.typicode.com/users:

    // src/hooks/useUsers.ts
    import { useQuery } from '@tanstack/react-query'
    
    const fetchUsers = async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/users')
      if (!response.ok) {
        throw new Error('Erro ao buscar usuários')
      }
      return response.json()
    }
    
    export const useUsers = () => {
      return useQuery(['users'], fetchUsers, {
        staleTime: 1000 * 60 * 5, // 5 minutos
        cacheTime: 1000 * 60 * 10, // 10 minutos
      })
    }
    
  4. Consumindo o Hook em uma Página:

    Agora, vamos utilizar o hook useUsers em uma página Next.js para exibir a lista de usuários.

    // src/pages/users.tsx
    import { useUsers } from '../hooks/useUsers'
    
    const UsersPage = () => {
      const { data, error, isLoading } = useUsers()
    
      if (isLoading) return <p>Carregando...</p>
      if (error) return <p>Erro ao carregar os dados.</p>
    
      return (
        <div>
          <h1>Lista de Usuários</h1>
          <ul>
            {data.map((user: any) => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
        </div>
      )
    }
    
    export default UsersPage
    

    Explicação do Exemplo

    • Cache e Stale Time: O React Query armazena os dados em cache e os considera frescos por 5 minutos (staleTime), evitando chamadas desnecessárias. Após esse período, novos fetches são feitos automaticamente quando necessário.
    • Error Handling: A gestão de erros é automática e simplificada, exibindo mensagens claras sem a necessidade de manipular estados manualmente.
    • React Query Devtools: Ferramenta essencial durante o desenvolvimento para inspecionar estados de queries e otimizar a aplicação.

Utilizando Context API:

Vantagens

  • Simplicidade e Facilidade de Uso: Não há necessidade de bibliotecas externas ou configuração complexa. O Context API é fácil de configurar e entender.
  • Redução de Prop Drilling: Elimina a necessidade de passar props por diversos níveis de componentes, reduzindo o acoplamento e aumentando a clareza do código.
  • Bom Desempenho para Estados Leves: Ideal para estados globais que mudam com pouca frequência, como o estado de autenticação, configurações de tema ou idioma.
  • Integração Natural com React e Next.js: Funcionamento perfeito com a estrutura de componentes do React e renderização do Next.js.

Exemplo Prático: Gerenciando Tema com Context API

Vamos criar um exemplo onde utilizamos o Context API para gerenciar um tema claro/escuro em uma aplicação Next.js. O exemplo inclui a criação de um contexto, um provider e a aplicação do contexto em uma página.

  1. Criando o Contexto de Tema:

    Primeiro, crie um contexto para gerenciar o estado de tema. O contexto fornecerá o tema atual e uma função para alternar entre os temas.

    // src/contexts/ThemeContext.tsx
    import { createContext, useContext, useState, ReactNode } from 'react'
    
    type Theme = 'light' | 'dark'
    
    interface ThemeContextType {
      theme: Theme
      toggleTheme: () => void
    }
    
    const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
    
    export const ThemeProvider = ({ children }: { children: ReactNode }) => {
      const [theme, setTheme] = useState<Theme>('light')
    
      const toggleTheme = () => {
        setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'))
      }
    
      return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>
    }
    
    export const useTheme = () => {
      const context = useContext(ThemeContext)
      if (!context) {
        throw new Error('useTheme must be used within a ThemeProvider')
      }
      return context
    }
    
  2. Configurando o Provider no Next.js:

    Para utilizar o contexto em toda a aplicação, envolva o componente raiz com o ThemeProvider no arquivo _app.tsx.

    // src/pages/_app.tsx
    import type { AppProps } from 'next/app'
    import { ThemeProvider } from '../contexts/ThemeContext'
    import '../styles/globals.css'
    
    function MyApp({ Component, pageProps }: AppProps) {
      return (
        <ThemeProvider>
          <Component {...pageProps} />
        </ThemeProvider>
      )
    }
    
    export default MyApp
    
  3. Consumindo o Contexto em uma Página:

    Agora, vamos criar uma página que utiliza o contexto para exibir e alternar o tema.

    // src/pages/index.tsx
    import { useTheme } from '../contexts/ThemeContext'
    
    const HomePage = () => {
      const { theme, toggleTheme } = useTheme()
    
      return (
        <div
          className={`flex min-h-screen items-center justify-center ${
            theme === 'light' ? 'bg-white text-black' : 'bg-black text-white'
          }`}
        >
          <div>
            <h1 className="text-3xl">Tema Atual: {theme === 'light' ? 'Claro' : 'Escuro'}</h1>
            <button className="mt-4 rounded bg-blue-500 px-4 py-2 text-white" onClick={toggleTheme}>
              Alternar Tema
            </button>
          </div>
        </div>
      )
    }
    
    export default HomePage
    

    Explicação do Exemplo

    • ThemeContext: Criamos um contexto que gerencia o tema atual (light ou dark) e uma função toggleTheme que alterna entre os dois temas.
    • Provider: O ThemeProvider envolve toda a aplicação, garantindo que qualquer componente possa acessar o contexto de tema sem necessidade de passar props.
    • Uso do Contexto: Na página index.tsx, utilizamos o hook useTheme para acessar e manipular o estado do tema de forma simples e direta.

Utilizando React Hook Form e Zod:

Vantagens do React Hook Form:

  • Desempenho Elevado: Menos re-renderizações graças à arquitetura otimizada, o que melhora a performance dos formulários.
  • Simplicidade de Integração: Interface simples com hooks para controle de formulários, validação e manipulação de estados.
  • Flexibilidade: Suporta validação customizada e integração com diversas bibliotecas de validação, como Zod, Yup, entre outras.
  • Gerenciamento Fácil de Erros e Estados: Controle simplificado de estados de erro, submissão e valores de entrada.

Vantagens do Zod:

  • Validação Tipada: Criação de schemas de validação fortemente tipados, que ajudam a capturar erros de forma antecipada no TypeScript.
  • Erros Detalhados: Retorna mensagens de erro específicas e amigáveis, facilitando o desenvolvimento e a experiência do usuário.
  • Declaração de Schemas Simples: Syntax intuitiva para definir validações complexas, incluindo tipos, tamanhos e formatos de dados.

Criando um Formulário com React Hook Form e Zod:

Vamos criar um exemplo de formulário simples de cadastro de usuário com validação utilizando o React Hook Form e Zod.

  1. Instalação das Dependências:

    Primeiramente, instale o React Hook Form e o Zod:

    npm install react-hook-form zod @hookform/resolvers
    
  2. Criando o Formulário com React Hook Form e Zod:

    Vamos criar um formulário com campos de nome, email e senha, validando as entradas com Zod.

    // src/pages/register.tsx
    import { useForm } from 'react-hook-form'
    import { z } from 'zod'
    import { zodResolver } from '@hookform/resolvers/zod'
    
    // Definindo o schema de validação com Zod
    const schema = z.object({
      name: z
        .string()
        .min(2, { message: 'O nome deve ter pelo menos 2 caracteres.' })
        .max(50, { message: 'O nome pode ter no máximo 50 caracteres.' }),
      email: z.string().email({ message: 'Insira um email válido.' }),
      password: z
        .string()
        .min(6, { message: 'A senha deve ter pelo menos 6 caracteres.' })
        .max(100, { message: 'A senha pode ter no máximo 100 caracteres.' }),
    })
    
    type FormData = z.infer<typeof schema>
    
    const RegisterPage = () => {
      // Configuração do React Hook Form com validação do Zod
      const {
        register,
        handleSubmit,
        formState: { errors },
      } = useForm<FormData>({
        resolver: zodResolver(schema),
      })
    
      const onSubmit = (data: FormData) => {
        console.log('Dados do Formulário:', data)
      }
    
      return (
        <div className="flex min-h-screen items-center justify-center bg-gray-100">
          <form
            onSubmit={handleSubmit(onSubmit)}
            className="rounded bg-white p-6 shadow-md"
            noValidate
          >
            <h2 className="mb-4 text-2xl font-semibold">Cadastro de Usuário</h2>
    
            <div className="mb-4">
              <label className="mb-1 block">Nome</label>
              <input type="text" {...register('name')} className="w-full rounded border p-2" />
              {errors.name && <p className="mt-1 text-sm text-red-500">{errors.name.message}</p>}
            </div>
    
            <div className="mb-4">
              <label className="mb-1 block">Email</label>
              <input type="email" {...register('email')} className="w-full rounded border p-2" />
              {errors.email && <p className="mt-1 text-sm text-red-500">{errors.email.message}</p>}
            </div>
    
            <div className="mb-4">
              <label className="mb-1 block">Senha</label>
              <input
                type="password"
                {...register('password')}
                className="w-full rounded border p-2"
              />
              {errors.password && (
                <p className="mt-1 text-sm text-red-500">{errors.password.message}</p>
              )}
            </div>
    
            <button
              type="submit"
              className="mt-4 w-full rounded bg-blue-500 p-2 font-semibold text-white"
            >
              Cadastrar
            </button>
          </form>
        </div>
      )
    }
    
    export default RegisterPage
    

    Explicação do Exemplo

    • Schema de Validação com Zod: Definimos um schema usando Zod para validar campos como nome, email e senha, garantindo que os dados estejam no formato correto antes de serem enviados.
    • Integração com React Hook Form: Utilizamos o hook useForm com o resolver do Zod (zodResolver) para conectar a validação ao formulário, simplificando o processo de validação e erro.
    • Gerenciamento de Erros: Os erros de validação são exibidos de forma clara ao usuário, melhorando a experiência com mensagens específicas para cada campo.

Utilizando ShadCN:

Vantagens

  • Componentes Prontos e Acessíveis: Fornece uma ampla gama de componentes pré-estilizados, acessíveis e prontos para uso, que podem ser personalizados com Tailwind CSS.
  • Integração com Tailwind CSS: Os componentes são desenvolvidos com Tailwind, o que permite que desenvolvedores utilizem classes utilitárias para estilização rápida e eficiente.
  • Personalização Completa: Oferece total controle sobre a aparência dos componentes, facilitando a adaptação da UI às necessidades do projeto.
  • Consistência e Rapidez no Desenvolvimento: Ajuda a manter uma consistência visual no projeto, acelerando o desenvolvimento com componentes reusáveis e estilizados.

Criando um Formulário de Login com ShadCN

Vamos criar um exemplo de formulário de login usando componentes do ShadCN. Esse exemplo demonstra como integrar ShadCN com Tailwind CSS para estilização customizada de componentes.

  1. Instalação do ShadCN e Tailwind CSS:

    Primeiro, instale o Tailwind CSS, que é um pré-requisito para o ShadCN, e configure no seu projeto Next.js:

    npm install tailwindcss postcss autoprefixer
    npx tailwindcss init -p
    

    Configure o arquivo tailwind.config.js:

    // tailwind.config.js
    module.exports = {
      content: ['./src/pages/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}'],
      theme: {
        extend: {},
      },
      plugins: [],
    }
    

    Crie o arquivo globals.css e adicione as importações do Tailwind CSS:

    /* src/styles/globals.css */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    

    Em seguida, instale o ShadCN:

    npm install @shadcn/ui
    
  2. Criando o Formulário de Login com ShadCN:

    Vamos criar um formulário de login utilizando os componentes Button e Input do ShadCN para estilização.

    // src/pages/login.tsx
    import { Button } from '@shadcn/ui'
    import { useForm } from 'react-hook-form'
    import { z } from 'zod'
    import { zodResolver } from '@hookform/resolvers/zod'
    
    // Definindo o schema de validação com Zod
    const loginSchema = z.object({
      email: z.string().email({ message: 'Insira um email válido.' }),
      password: z.string().min(6, { message: 'A senha deve ter pelo menos 6 caracteres.' }),
    })
    
    type LoginFormData = z.infer<typeof loginSchema>
    
    const LoginPage = () => {
      const {
        register,
        handleSubmit,
        formState: { errors },
      } = useForm<LoginFormData>({
        resolver: zodResolver(loginSchema),
      })
    
      const onSubmit = (data: LoginFormData) => {
        console.log('Login realizado com sucesso:', data)
      }
    
      return (
        <div className="flex min-h-screen items-center justify-center bg-gray-50">
          <form
            onSubmit={handleSubmit(onSubmit)}
            className="space-y-4 rounded bg-white p-6 shadow-md"
          >
            <h2 className="text-center text-2xl font-bold">Login</h2>
    
            <div>
              <label className="block text-sm font-medium text-gray-700">Email</label>
              <input
                type="email"
                {...register('email')}
                className="mt-1 w-full rounded border p-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
              {errors.email && <p className="mt-1 text-sm text-red-500">{errors.email.message}</p>}
            </div>
    
            <div>
              <label className="block text-sm font-medium text-gray-700">Senha</label>
              <input
                type="password"
                {...register('password')}
                className="mt-1 w-full rounded border p-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
              {errors.password && (
                <p className="mt-1 text-sm text-red-500">{errors.password.message}</p>
              )}
            </div>
    
            <Button type="submit" className="w-full bg-blue-500 hover:bg-blue-600">
              Entrar
            </Button>
          </form>
        </div>
      )
    }
    
    export default LoginPage
    

    Explicação do Exemplo

    • Componentes ShadCN: Utilizamos o Button do ShadCN para estilizar o botão de envio com uma aparência moderna e responsiva, integrando perfeitamente com o Tailwind CSS.
    • Integração com React Hook Form e Zod: O formulário é gerenciado pelo React Hook Form, e as validações são tratadas pelo Zod, garantindo entradas seguras e feedback claro para o usuário.
    • Personalização com Tailwind CSS: O layout e os estilos foram personalizados utilizando classes utilitárias do Tailwind CSS, oferecendo controle completo sobre o design da interface.

Conclusão

A construção de um projeto front-end envolve a escolha cuidadosa de bibliotecas que otimizem o fluxo de trabalho, melhorem a experiência do usuário e garantam a manutenção do código a longo prazo. Com a combinação de ferramentas como React Query, Context API, React Hook Form, Zod, ShadCN, e Tailwind CSS, conseguimos criar aplicações robustas, rápidas e escaláveis.

  • React Query facilita o consumo de APIs e o gerenciamento de cache, eliminando a complexidade do estado assíncrono e proporcionando uma experiência de usuário mais fluida.
  • Context API permite a criação de contextos que simplificam o compartilhamento de estado entre componentes, promovendo uma arquitetura mais limpa e menos dependente de props.
  • React Hook Form e Zod são fundamentais para o gerenciamento eficiente de formulários, trazendo uma validação tipada e um desempenho superior, ideal para formulários complexos e interativos.
  • ShadCN e Tailwind CSS formam uma poderosa dupla para estilização, combinando a customização total de Tailwind com a acessibilidade e os componentes prontos do ShadCN, acelerando o desenvolvimento e garantindo consistência visual.