Pular para conteúdo

Modelo de Referência para React.js

React.js é uma biblioteca de JavaScript de código aberto, mantida pelo Facebook e uma comunidade de desenvolvedores, projetada para criar interfaces de usuário (UI) eficientes e interativas para aplicativos da web. É frequentemente utilizada para construir componentes reutilizáveis e dinâmicos, permitindo o desenvolvimento de interfaces de usuário escaláveis e de fácil manutenção.

O React foi desenvolvido com foco na construção de interfaces de usuário declarativas, o que significa que os desenvolvedores podem descrever como a interface deve parecer em diferentes estados, e o React atualiza automaticamente a visualização quando os dados mudam. Ele utiliza uma abordagem baseada em componentes, onde os diferentes elementos da interface são divididos em componentes independentes e encapsulados, facilitando a reutilização, manutenção e teste de código.

Uma das características principais do React é o seu modelo de renderização virtual (Virtual DOM). Em vez de atualizar diretamente o DOM (Document Object Model) do navegador sempre que ocorre uma alteração nos dados, o React cria uma representação virtual do DOM na memória. Ele compara essa representação virtual com o DOM atual e, somente após identificar as diferenças, atualiza eficientemente apenas as partes do DOM que foram alteradas. Isso resulta em melhor desempenho e uma experiência do usuário mais responsiva.

Além disso, o React pode ser combinado facilmente com outras bibliotecas e frameworks, permitindo aos desenvolvedores integrá-lo com diversas ferramentas e tecnologias para atender às necessidades específicas de seus projetos.

Em resumo, o React.js oferece uma abordagem eficiente para criar interfaces de usuário dinâmicas, modulares e escaláveis em aplicações web, proporcionando aos desenvolvedores uma maneira mais eficaz de construir e manter interfaces de usuário interativas.

Versões/Releases aplicáveis

A Magna Sistemas, por meio do MITH (Magna Innovation Technology Hub), estabelece padrões para o desenvolvimento de projetos. Em projetos React, há semelhanças importantes que demandam planejamento cuidadoso, definição de metas claras e estratégias eficientes para obter resultados consistentes. Assim como uma empresa busca maximizar a eficiência e a colaboração de sua equipe, a padronização do uso de bibliotecas promove a coesão e a reutilização de código, aumentando a produtividade e a qualidade dos projetos. A adoção de uma biblioteca padrão oferece uma base sólida para o desenvolvimento, garantindo consistência, manutenibilidade e escalabilidade nos projetos.

Os arquitetos e líderes têm a liberdade de preferir outras bibliotecas relevantes, porém, é importante destacar que a padronização contribui para melhorar a performance e facilitar a colaboração entre as equipes

React Router Dom

Sem dúvida, o React Router Dom ⧉ é a biblioteca de roteamento mais amplamente utilizada no desenvolvimento React. Sua documentação é abrangente e completa, fornecendo recursos poderosos para gerenciar a navegação em aplicativos React. Com o React Router Dom, é possível criar rotas dinâmicas, aninhadas e protegidas, facilitando a criação de interfaces de usuário complexas e responsivas. A popularidade e a maturidade desta biblioteca a tornam uma escolha confiável e recomendada para projetos React que exigem um sistema de roteamento robusto e flexível.

    npm install react-router-dom

Install React Router Dom

Axios

O Axios ⧉ é uma das bibliotecas mais amplamente utilizadas em projetos JavaScript, oferecendo uma variedade de recursos úteis para facilitar o trabalho com requisições HTTP. Com uma API simples e intuitiva, o Axios permite realizar requisições HTTP de forma assíncrona, lidando com promessas de maneira eficiente. Além disso, o Axios oferece suporte a interceptores, permitindo o controle e manipulação das requisições e respostas de forma centralizada. Sua popularidade se dá devido à sua facilidade de uso, desempenho confiável e ampla adoção na comunidade de desenvolvedores JavaScript.

    npm install axios

Install Axios

Material UI

O Material UI ⧉ é a principal biblioteca de interface de usuário (UI) para o React, oferecendo uma ampla gama de componentes que possibilitam um desenvolvimento ágil, limpo e livre de complicações. Com uma abordagem baseada no design do Material Design, do Google, o Material UI fornece componentes pré-estilizados e responsivos, juntamente com um sistema de grade flexível, ícones personalizáveis e temas adaptáveis. Essa biblioteca facilita a criação de interfaces modernas e agradáveis visualmente, permitindo que os desenvolvedores se concentrem mais na lógica de negócios e menos na estilização manual. O Material UI é altamente popular e amplamente adotado, garantindo uma experiência de desenvolvimento eficiente e resultados visualmente atraentes.

    npm install @mui/material @emotion/react @emotion/styled
    npm install @fontsource/roboto
    npm install @mui/icons-material

Install MUI

No arquivo index da raiz do seu projeto import a fonte e os icones:

<link
    rel="stylesheet"
    href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>

<link
    rel="stylesheet"
    href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>

Date-fns

Em muitos projetos, é necessário realizar manipulação e comparação de datas. O Date-fns ⧉ é uma biblioteca que nos permite realizar essas tarefas de maneira simples e eficiente. Date-fns oferece funções poderosas para realizar formatação de datas, cálculos de diferença, adição/subtração de períodos de tempo e muito mais. Além disso, essa biblioteca também possui suporte para diferentes idiomas e é altamente otimizada para desempenho. Com o Date-fns, é possível lidar com desafios relacionados a datas de forma confiável e eficaz, tornando-o uma escolha popular para projetos que envolvem manipulação de datas.

    npm install date-fns

Install Date-fns

React Hook Form

O React Hook Form ⧉ é atualmente considerado o melhor gerenciador de formulários no ecossistema React. Com seus hooks poderosos, essa biblioteca permite trabalhar de forma simples e ágil com formulários complexos. O React Hook Form simplifica o gerenciamento de estado e validação dos campos de formulário, facilitando a obtenção dos valores dos campos, manipulação de erros e envio dos dados. Além disso, seu suporte a validação assíncrona, campos dinâmicos e integração com bibliotecas populares de UI, como o Material UI, tornam o React Hook Form uma escolha popular entre os desenvolvedores. Com essa biblioteca, é possível criar formulários robustos e eficientes, economizando tempo e esforço no desenvolvimento.

    npm install react-hook-form

Install React Hook Form

Yup

O Yup ⧉ é uma biblioteca de validação de esquemas que oferece uma solução simples e poderosa para a validação de dados no JavaScript. Com o Yup, é possível definir esquemas de validação de forma declarativa, especificando as regras e restrições para cada campo. Essa biblioteca permite verificar se os dados estão corretos de acordo com as regras definidas, como tipos de dados, valores mínimos/máximos, formatos de email, entre outros. Além disso, o Yup também suporta validação assíncrona, permitindo a validação de dados em tempo real, como chamadas de API. Com sua sintaxe intuitiva e flexibilidade, o Yup é amplamente utilizado para garantir a integridade e consistência dos dados em aplicações JavaScript.

    npm install yup

Install YUP

Zustand

Gerenciar estados globais com React sempre foi um desafio, mesmo com o surgimento de ferramentas poderosas como a Context API. A necessidade de ter todos os estados no mesmo contexto muitas vezes torna o processo de desenvolvimento mais complexo. No entanto, o Zustand ⧉ resolve esse problema de maneira simples e objetiva, sem a necessidade de utilizar Context ou implementar mecanismos elaborados.

Com o Zustand, é possível criar e gerenciar estados globais de forma fácil e eficiente. Essa biblioteca utiliza hooks para gerenciar o estado da aplicação, permitindo que diferentes componentes acessem e modifiquem esses estados de maneira transparente. O Zustand também oferece recursos avançados, como atualizações otimizadas e assíncronas, além de suporte para lidar com estados imutáveis.

    npm install zustand

Install Zustand

Pontos positivos

O React é possui uma grande oferta de mão de obra no mercado. Devido à popularidade e ao uso generalizado do React.js, existe uma vasta comunidade de desenvolvedores proficientes nessa tecnologia. Isso significa que há uma abundância de profissionais qualificados, bem como uma ampla gama de recursos educacionais, tutoriais e documentação disponíveis para aprendizado.

  1. Componentização: O React é baseado em componentes reutilizáveis. Isso permite a criação de interfaces complexas divididas em componentes menores e independentes, facilitando a manutenção, teste e reutilização de código.

  2. Virtual DOM: O React utiliza um Virtual DOM para aumentar o desempenho. Ele cria uma representação virtual do DOM na memória e, quando há alterações nos dados, compara essa representação com o DOM atual, atualizando apenas as partes que mudaram. Isso resulta em uma renderização mais rápida e eficiente.

  3. Performance: Graças ao Virtual DOM e à capacidade de atualizar somente as partes necessárias do DOM, o React tende a ser mais rápido em comparação com abordagens tradicionais de atualização do DOM.

  4. JSX: O React utiliza JSX, uma extensão de sintaxe JavaScript que permite escrever códigos HTML dentro do JavaScript. Isso facilita a criação de componentes e melhora a legibilidade do código.

  5. Biblioteca Focada: O React é uma biblioteca, não um framework completo. Isso dá aos desenvolvedores mais liberdade para escolher e integrar outras bibliotecas e ferramentas conforme necessário, adaptando-se melhor aos requisitos do projeto.

  6. Comunidade Ativa: O React possui uma comunidade vasta e ativa, o que significa amplo suporte, documentação extensa, tutoriais e uma grande quantidade de recursos disponíveis.

  7. Fácil Integração: Pode ser facilmente integrado com outras bibliotecas e frameworks, permitindo a criação de aplicações complexas.

  8. Desenvolvimento Declarativo: O React utiliza uma abordagem declarativa para criar interfaces de usuário. Em vez de manipular diretamente o DOM, os desenvolvedores descrevem como a interface deve ser em diferentes estados e deixam o React cuidar das atualizações.

  9. Suporte de Ferramentas: Existem muitas ferramentas de desenvolvimento, como React DevTools, que facilitam a depuração, teste e análise de desempenho de aplicativos React.

Pontos negativos

  1. Níveis de Proffissionais: Para quem está começando, a curva de aprendizado do React pode ser alta, porém temos uma oferta alta de profissionais Junior, mas poucos profissionais Plenos e Senior com níveis técnicos adequeado.

  2. Configuração Complexa: O processo inicial de configuração de um projeto React pode ser complexo, especialmente para iniciantes. Embora existam ferramentas como o Vite.js que simplificam essa tarefa, configurar manualmente um projeto pode ser desafiador.

  3. Rápida Evolução: O ecossistema React e suas bibliotecas estão em constante evolução. Isso significa que as atualizações frequentes podem exigir que os desenvolvedores adaptem seus códigos para versões mais recentes, o que pode ser desafiador, especialmente em projetos grandes ou legados.

  4. Decisões de Arquitetura: Como o React oferece muita liberdade na estruturação do código, isso pode resultar em escolhas arquiteturais problemáticas se não houver um planejamento cuidadoso. Decidir sobre o estado, gerenciamento de estado global e fluxo de dados pode ser complicado para equipes sem experiência prévia.

  5. Documentação e Suporte Constantemente Atualizados: Embora o React tenha uma boa documentação e uma comunidade ativa, as mudanças frequentes podem levar a desatualizações na documentação ou em recursos online, o que às vezes pode dificultar a resolução de problemas ou o aprendizado para iniciantes.

  6. Tamanho do Pacote: Às vezes, aplicações React podem ter um tamanho considerável devido às dependências e à estrutura da biblioteca. Isso pode afetar o desempenho, especialmente em dispositivos com conexões de internet mais lentas.

  7. Compatibilidade com SEO: Apesar das melhorias, o React inicialmente tinha desafios em relação à otimização para mecanismos de busca (SEO). Embora soluções tenham sido propostas (como renderização no lado do servidor), ainda pode ser um obstáculo para alguns projetos.

  8. Escolha de Ferramentas: Devido à natureza flexível do React, pode ser desafiador para os desenvolvedores escolherem entre uma grande variedade de bibliotecas e ferramentas disponíveis, o que pode levar a decisões difíceis e à necessidade de constante avaliação e atualização de tecnologias.

Referências

React ⧉

React Router Dom ⧉

Axios ⧉

Material UI ⧉

Date-fns ⧉

React Hook Form ⧉

Yup ⧉

Zustand ⧉

Iniciando um Projeto

TypeScript

Exitem várias razões pelas quais você pode deve usar TypeScript em seu projeto:

  1. Tipagem estática: O TypeScript introduz a tipagem estática no JavaScript, permitindo que você declare e verifique os tipos das variáveis, parâmetros de função e retornos de função. Isso ajuda a identificar erros de digitação, detectar erros em tempo de compilação e melhorar a qualidade do código.

  2. Maior escalabilidade: Com a adição de tipos estáticos, o TypeScript torna mais fácil lidar com projetos de grande escala. À medida que seu código cresce, a tipagem ajuda a evitar erros comuns, facilita a manutenção do código e permite uma melhor colaboração entre a equipe.

  3. Melhorias na IDE: O TypeScript oferece suporte a recursos avançados de autocompletar, navegação e refatoração em ambientes de desenvolvimento integrados (IDEs). Isso significa que você terá um desenvolvimento mais produtivo, com sugestões inteligentes de código, documentação incorporada e uma experiência geral mais agradável enquanto escreve código.

  4. Ecossistema JavaScript: O TypeScript é construído sobre a base do JavaScript e é compatível com todas as bibliotecas e frameworks existentes. Isso significa que você pode aproveitar todo o rico ecossistema JavaScript.

  5. Futuro da linguagem: O TypeScript é desenvolvido e mantido pela Microsoft e tem ganhado popularidade rapidamente. Ele continua a receber atualizações regulares e novos recursos, tornando-se uma opção sólida para projetos a longo prazo. Usar o TypeScript pode ajudar a garantir que seu código esteja atualizado com as melhores práticas e padrões da comunidade de desenvolvimento.

Vite

Vite ⧉ é um servidor de desenvolvimento Javascript que que é usado po padrão pelo Vue, sendo também e é uma solução muito poderosa para React.js, tem suporte para Typescript e um CLI intuiva. Atualmente é a forma mais rapida se iniciar um projeto React.

Utilize o codigo a seguir para inicial um projeto.

    npm init vite@latest

Informe um nome para seu projeto e escolha as opções a seguir:

CLI Vite

Execute abaixo os

    cd projeto-referencia
    npm install
    npm run dev

Localhost

Verifique se seu projeto esta executando.

React + Vite

Preparando projeto

Removendo arquivos desnecessários

Quando fazemos a instalação através de uma CLI é necessário fazer uma limpeza, excluíndo assim os arquivos de exemplo.

Apague os arquivos react.svg, App.css, index.css

Removendo Arquivos

Instalando o ESLint e Prettier

    npm install eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev

 ESLint e Prettier

No diretório do projeto, crie/edite um arquivo chamado .eslintrc.cjs e adicione o seguinte conteúdo:

/* eslint-env node */

module.exports = {
    root: true,
    env: { browser: true, es2020: true },
    extends: [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:react-hooks/recommended",
        "airbnb",
        "airbnb/hooks",
        "plugin:@typescript-eslint/recommended",
    ],
    parser: "@typescript-eslint/parser",
    parserOptions: {
        ecmaVersion: 2021,
        sourceType: "module",
        project: true,
        tsconfigRootDir: __dirname,
        ecmaFeatures: {
            jsx: true,
        },
    },
    plugins: ["react-hooks", "@typescript-eslint", "react-refresh"],
    extends: [
        "airbnb",
        "airbnb/hooks",
        "plugin:@typescript-eslint/recommended",
    ],
    rules: {
        "react-refresh/only-export-components": [
            "warn",
            { allowConstantExport: true },
        ],
        "react/jsx-filename-extension": [1, { extensions: [".tsx"] }],
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn",
        "import/extensions": [
            "error",
            "ignorePackages",
            { ts: "never", tsx: "never" },
        ],
        "import/no-extraneous-dependencies": [
            "error",
            { devDependencies: true },
        ],
        "@typescript-eslint/explicit-module-boundary-types": "off",
        "@typescript-eslint/no-explicit-any": "off",
        "@typescript-eslint/no-non-null-assertion": "off",
        "react/react-in-jsx-scope": "off",
    },
};

Essa configuração estende o padrão Airbnb e adiciona as regras específicas para o React e o TypeScript.

Instale as dependências do Prettier

No diretório do projeto, abra o terminal e execute o seguinte comando para instalar as dependências do Prettier:

    npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev

Crie um arquivo com o nome .prettierrc e adicione o seguinte conteúdo:

{
    "semi": true,
    "trailingComma": "all",
    "singleQuote": true,
    "printWidth": 80,
    "tabWidth": 2,
    "arrowParens": "always",
    "endOfLine": "auto"
}

Essa configuração alinha-se com as diretrizes de estilo do Airbnb e define as seguintes opções:

  • "semi": true: Adiciona ponto e vírgula no final das linhas.
  • "trailingComma": "all": Adiciona vírgula no final de arrays e objetos literais.
  • "singleQuote": true: Utiliza aspas simples para delimitar strings.
  • "printWidth": 80: Limita o número de caracteres por linha a 80.
  • "tabWidth": 2: Define o tamanho da tabulação como 2 espaços.
  • "arrowParens": "always": Adiciona parênteses em torno dos parâmetros de setas de função sempre que houver mais de um parâmetro.
  • "endOfLine": "auto": Determina automaticamente o formato de quebra de linha com base no sistema operacional (CRLF no Windows e LF no macOS/Linux).

Abra o arquivo package.json e adicione os seguintes scripts:

    "scripts": {
      "dev": "vite",
      "build": "tsc && vite build",
      "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
      "lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix",
      "preview": "vite preview"
    },

Script

Para garantir que o Prettier esteja formatando o código ao salvar, siga estas etapas:

  1. Certifique-se de ter a extensão "Prettier - Code formatter" instalada no Visual Studio Code.
  2. Abra as configurações do Visual Studio Code (File -> Preferences -> Settings ou usando o atalho Ctrl + ,).
  3. Na barra de pesquisa, digite "Format On Save" e verifique se a opção "Editor: Format On Save" está marcada.

Format on Save

Siga as etapas abaixo para criar o arquivo settings.json no diretório .vscode do seu projeto:

  1. No diretório raiz do seu projeto, verifique se a pasta .vscode existe. Caso não exista, crie-a manualmente.
  2. Dentro da pasta .vscode, crie um arquivo chamado settings.json.
  3. Abra o arquivo settings.json recém-criado e adicione a seguinte configuração:
{
    "editor.formatOnSave": true
}

No Visual Studio Code e pressione Ctrl + , (ou vá em File -> Preferences -> Settings) para abrir as configurações do Visual Studio Code. Na barra de pesquisa, digite Editor: Default Formatter e clique em Prettier - Code formatter

Prettier formatter

Reinicie o projeto no VsCode.

Estrutura de pastas

$ raiz
.
├── src
│   ├── @types.tsx                          # contém os types globais do projeto
│   ├── assets                              # recursos estáticos como imagens, ícones, etc
│   ├── components                          # componentes reutilizáveis
│   │   ├── NomeComponente                  # pasta do componente
│   │   │    ├── index.tsx                  # contém exportações da pasta
│   │   │    ├── nomeComponente.tsx         # componente
│   │   │    ├── @types.tsx                 # contém os types do componente
│   │   │    └── styles.tsx                 # contém um objeto em CssJs estilizado par ao material ui
│   │   ├── NomeComponenteComplexos         # pasta de componentes complexos
│   │   │   ├── index.tsx                   # contém exportações da pasta
│   │   │   ├── @types.tsx                  # contém os types do componentes
│   │   │   ├── NomeComponenteRoot.tsx      # componente principal
│   │   │   ├── nomeComponentente           # nome do componente
│   │   │   │    ├── index.tsx              # contém exportações da pasta
│   │   │   │    ├── nomeComponente.tsx     # componente
│   │   │   │    ├── @types.tsx             # contém os types do componente
│   │   │   │    └── styles.tsx             # contém um objeto em CssJs estilizado par ao material ui
│   │   └── ComponentesAgrupados            # pasta de componentes agrupados
│   │       ├──nomeComponentente            # nome do componente
│   │       │    ├── index.tsx              # contém exportações da pasta
│   │       │    ├── nomeComponente.tsx     # componente
│   │       │    ├── @types.tsx             # contém os types do componente
│   │       │    └── styles.tsx             # contém um objeto em CssJs estilizado par ao material ui
│   │       ├── @types.tsx                  # contém os types do componente
│   │       ├── NomeComponenteRoot.tsx      # recursos estáticos como imagens, ícones, etc
│   │       ├── NomeComponenteRoot.tsx      # recursos estáticos como imagens, ícones, etc
│   │       └── index.tsx                   # contém exportações da pasta
│   ├── helpers                             # funções auxiliares
│   │   ├── index.tsx                       # contém exportações da pasta
│   │   └── nomeFuncaotsx                   # um arquivo para cada função
│   ├── hooks                               # hooks customizados
│   │   ├── index.tsx                       # contém exportações da pasta
│   │   └── nomeHook.tsx                    # um arquivo para cada hook custom
│   ├── pages                               # páginas da aplicação
│   │    └──NomePagina                      # pasta com o nome da página
│   │       ├── @types.tsx                  # contém type da pasta
│   │       ├── index.tsx                   # contém index com a exportação da page
│   │       ├── nomePagina.tsx              # componente que monta a página
│   │       ├── routes.tsx                  # arquivo que exporta as rotas
│   │       └── subPagina                   # caso seu projeto tenha pagina alinhadas
│   │           ├── @types.tsx              # contém type da pasta
│   │           ├── index.tsx               # contém index com a exportação da page
│   │           └── nomePagina.tsx          # componente que monta a página
│   ├── routes                              # definição de rotas
│   │   ├── index.tsx                       # contém exportações da pasta
│   │   └── useRouter.tsx                   # hook que é responsavel por importar rotas cadastrar-las no router dom
│   ├── storeds                             # gerenciamento de estado global
│   │   ├── index.tsx                       # contém exportações da pasta
│   │   └── nomeStoreds                     # uma pasta para cada estado global
│   │   │   ├── index.tsx                   # contém exportações da pasta
│   │   │   └── nomeStoreds.tsx             # arquivo contendo do zustand estado global
│   ├── theme                               # configuração e estilos do tema
│   │   ├── index.tsx                       # contém exportações da pasta
│   │   └── nomeStoreds.tsx                 # um arquivo para cada estado global
├── main.tsx                                # arquivo de entrada principal
└── App.tsx                                 # componente raiz da aplicação

Design Patterns

Design pattern, ou padrão de design, é uma abordagem comum para resolver problemas recorrentes no desenvolvimento de software. Ao utilizar temos soluções testadas e comprovadas que podem melhorar a estrutura, a manutenibilidade e a escalabilidade de um projeto. Esses padrões fornecem diretrizes e práticas recomendadas que ajudam os desenvolvedores a escreverem código mais organizado, modular e reutilizável.

HOC - Higher-Order Component

HOC, ou Higher-Order Component, é um padrão de design em React que permite reutilizar lógica entre componentes. Um HOC é uma função que recebe um componente e retorna um novo componente com funcionalidades adicionais. Ele encapsula a lógica compartilhada, como autenticação, manipulação de dados ou lógica de renderização condicional, permitindo que vários componentes se beneficiem dessa funcionalidade sem a necessidade de repetir código. Os HOCs são uma maneira flexível e poderosa de estender a funcionalidade dos componentes React e promover a reutilização de código de forma modular.

The Provider Pattern

O Provider Pattern é um padrão de design utilizado no React para lidar com o compartilhamento de dados ou funcionalidades em vários componentes. Ele envolve a criação de um componente especial chamado Provider, que encapsula o estado ou as funções que serão compartilhadas com outros componentes. Os componentes filhos podem então acessar esses dados ou funcionalidades usando um componente especial chamado Consumer. O Provider Pattern é especialmente útil quando se trata de compartilhar informações globais, como o tema do aplicativo, dados do usuário autenticado ou um estado global gerenciado por um gerenciador de estado, como Redux. Ele oferece uma forma eficiente e escalável de fornecer informações e funcionalidades em toda a árvore de componentes, evitando a necessidade de passar manualmente os dados por várias camadas de componentes.

Presentational & Container

O padrão de componentes Presentational e Container é uma abordagem amplamente utilizada no desenvolvimento de aplicações React. Nesse padrão, os componentes presentacionais são responsáveis pela exibição da interface de usuário, enquanto os componentes containers gerenciam o estado e a lógica de negócios. Essa separação permite uma melhor organização e reutilização do código, facilitando a compreensão, manutenção e teste dos componentes. Os componentes presentacionais são independentes e reutilizáveis, enquanto os componentes containers coordenam a interação entre os componentes e lidam com a lógica específica do aplicativo.

React Hooks - Custom Hooks

Custom Hooks são funções personalizadas no React que permitem aos desenvolvedores extrair lógica compartilhada entre componentes funcionais em unidades reutilizáveis. Eles permitem encapsular e compartilhar comportamentos complexos, como manipulação de estado, chamadas de API ou lógica de formulário, entre diferentes componentes. Ao criar um Custom Hook, os desenvolvedores podem modularizar e organizar seu código de forma eficiente, promovendo a reutilização e evitando a repetição de lógica em vários lugares. Os Custom Hooks são uma ferramenta poderosa para a criação de componentes mais legíveis, flexíveis e fáceis de manter no React, contribuindo para um desenvolvimento mais eficiente e modular.

Compound Component

O padrão de Compound Component é uma abordagem de design no React que permite a criação de componentes que trabalham juntos como um grupo coeso, permitindo maior flexibilidade e controle sobre a estrutura e comportamento desses componentes. O padrão de Componente Composto é particularmente útil quando se deseja fornecer uma interface de usuário flexível e personalizável, permitindo que os usuários combinem e configurem os componentes de acordo com suas necessidades específicas. Esse padrão é geralmente implementado com o uso de props especiais que controlam o estado e o comportamento dos componentes filhos, permitindo que o componente composto se torne uma API intuitiva e coesa. O padrão de Componente Composto é uma poderosa técnica de design que promove a reutilização, a modularidade e a extensibilidade dos componentes React.

React Conditional Rendering

O padrão de renderização condicional no React é uma abordagem de design que permite exibir elementos ou componentes diferentes com base em uma condição específica. Isso pode ser útil para exibir conteúdo condicional, alternar entre diferentes visualizações ou lidar com casos de erro. Ao utilizar esse padrão, os desenvolvedores podem controlar de forma dinâmica o que é exibido na interface do usuário, adaptando-se a diferentes estados ou lógicas de aplicativo. O React Conditional Rendering é uma técnica poderosa que permite criar interfaces de usuário mais flexíveis e personalizadas, tornando os aplicativos React mais interativos e responsivos.

Layout

É verdadeiramente impossível conceber um sistema web sem considerar um design responsivo. Em alguns projetos, temos designers, profissionais de UX/UI e até mesmo equipes de marketing trabalhando conosco como parceiros. No entanto, é responsabilidade do front-end transformar as ideias desses profissionais em realidade no sistema.

Existem várias abordagens para construir um layout, desde conceitos básicos até conceitos avançados. Porém, depender exclusivamente desses profissionais para desenvolver toda a camada de responsividade demandaria tempo e um profundo conhecimento do assunto.

Felizmente, nossos projetos já contam com um Design System (Material UI). Nele, temos recursos que nos permitem construir layouts de forma rápida e segura. O Material UI oferece componentes pré-estilizados e responsivos, juntamente com um sistema de grade flexível, facilitando a criação de interfaces modernas e adaptáveis a diferentes dispositivos. Com essa ferramenta em mãos, podemos criar layouts de maneira eficiente, mantendo a consistência visual e a melhor experiência para os usuários.

Construindo

No projeto de referência, o layout utilizado é composto por diferentes elementos.

Na parte superior, temos o navbar, que representa a barra de navegação do sistema. Essa área geralmente contém menus, botões de login, logo da empresa e outros elementos de navegação importantes.

À esquerda, temos uma sessão que representa a área de menu. Essa seção é responsável por exibir opções de navegação, como links para diferentes páginas, categorias ou funcionalidades do sistema. Essa área ajuda os usuários a acessarem rapidamente as diferentes partes do sistema.

À direita, temos outra sessão que representa a área de conteúdo. Nessa seção, são exibidos os dados, informações, formulários ou qualquer outro conteúdo relevante para o usuário. Essa área é dinâmica e pode ser atualizada conforme a interação do usuário ou o contexto do sistema.

Na parte inferior, temos o footer, que representa o rodapé do sistema. Essa área geralmente contém informações adicionais, como links para políticas de privacidade, termos de uso, informações de contato ou direitos autorais.

Layoyt

Utilizando o design pattern de Custon Hook, vamos criar a estrutura de layout:

/*
    src/layouts/useLayout.tsx
    Esse arquivo ira conter as funcionalidades de nosso layout,
    ocultar ou exibir o menu lateral 
*/

import { useState } from "react";

export const useLayout = () => {
    const [open, setOpen] = useState(true);

    const handleToggleMenu = () => {
        setOpen(!open);
    };

    return {
        open,
        handleToggleMenu,
    };
};
/*
    src/layouts/layoutMain.tsx
    Estrura do layout principal, em alguns sistemas
    podemos ter mais de um layout
*/

import { Box, Grid, Stack, Paper } from "@mui/material";
import { Footer } from "../components/footer";
import { Props } from "./@types";
import { useLayout } from "./useLayout";

export const LayoutMain = ({ children }: Props) => {
    const { open, handleToggleMenu } = useLayout();

    return (
        <>
            <Stack minHeight={"100vh"}>
                <Box>
                    <h1>Navbar</h1>
                </Box>
                <Stack flexGrow={1} component={Paper}>
                    <Grid container flexGrow={1}>
                        <Grid
                            item
                            xs={12}
                            md={3}
                            display={open ? "block" : "none"}
                            sx={{ transition: "all 1s ease" }}
                        >
                            <h1>Menu</h1>
                        </Grid>
                        <Grid item flexGrow={1}>
                            {children}
                        </Grid>
                    </Grid>
                </Stack>
                <Stack component={Paper}>
                    <h1>Footer</h1>
                </Stack>
            </Stack>
        </>
    );
};
/*
    src/layouts/@types.tsx
    Nesse arquivo ira conter a tipagem de nosso layout
*/

export type Props = {
    children: JSX.Element | JSX.Element[];
};
/*
    src/layouts/index.tsx
    No index iremos exportar todos os layouts existente.
*/

export * from "./layoutMain";

Seu sistema deve estar com os aquivos App.tx e main.tsx da seguinte forma:

/*
    src/App.tsx
    // CssBaseline fará o reset de sua folha de 
    estilo padrão para o estilo inicial do Material UI
*/

import { LayoutMain } from "./layouts/layoutMain";
import { CssBaseline } from "@mui/material";

export const App = () => {
    return (
        <>
            <CssBaseline />
            <LayoutMain>
                <h1>Conteúdo interno</h1>
            </LayoutMain>
        </>
    );
};
/*
    src/main.tsx
*/

import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);

Execute o comando abaixo para ver o resultado inicial

  npm run dev

A saída deverá ser como esse abaixo:

layout main

Vamos constuir nossso Navbar, Menu e Footer, em nosso exemplo eles serão simples, mas você deve cria-lo para atender a necessidade de seu sistema.

Vamos construir o Navbar:

/*
  src\components\navbar\navbar.tsx
*/

import { AppBar, IconButton, Toolbar, Typography, Button } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import { styleIconButton, styleText } from "./styles";
import { Props } from "./@types";

export const Navbar = ({ handleToggleMenu }: Props) => {
    return (
        <AppBar position="static" elevation={0}>
            <Toolbar>
                <IconButton
                    size="large"
                    edge="start"
                    color="inherit"
                    aria-label="menu"
                    sx={styleIconButton}
                    onClick={() => handleToggleMenu()}
                >
                    <MenuIcon />
                </IconButton>
                <Typography variant="h6" component="div" sx={styleText}>
                    Projeto Referencia
                </Typography>
                <Button color="inherit">Login</Button>
            </Toolbar>
        </AppBar>
    );
};
/*
  src\components\navbar\styles.tsx
*/

import { StyleSx } from "../../@types";

export const styleIconButton: StyleSx = { mr: 2 };

export const styleText: StyleSx = { flexGrow: 1 };
/*
  src\components\navbar\@types.tsx
*/

export type Props = {
    handleToggleMenu: () => void;
};
/*
    src\components\navbar\index.tsx
*/

export * from "./navbar";

Agora iremos fazer o Menu:

/*
  src\components\menu\menu.tsx
*/

import ArticleIcon from "@mui/icons-material/Article";
import InfoIcon from "@mui/icons-material/Info";
import PeopleAltIcon from "@mui/icons-material/PeopleAlt";
import WorkspacesIcon from "@mui/icons-material/Workspaces";
import {
    Divider,
    ListItemIcon,
    ListItemText,
    MenuItem,
    MenuList,
    Paper,
} from "@mui/material";
import { stylePaper } from "./styles";

export const Menu = () => {
    return (
        <Paper sx={stylePaper}>
            <MenuList>
                <MenuItem>
                    <ListItemIcon>
                        <ArticleIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>Documentação</ListItemText>
                </MenuItem>
                <MenuItem>
                    <ListItemIcon>
                        <PeopleAltIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>Equipe</ListItemText>
                </MenuItem>
                <MenuItem>
                    <ListItemIcon>
                        <WorkspacesIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>Reuniões</ListItemText>
                </MenuItem>
                <Divider />
                <MenuItem>
                    <ListItemIcon>
                        <InfoIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>Sobre o MITH</ListItemText>
                </MenuItem>
            </MenuList>
        </Paper>
    );
};
/*
  src\components\menu\styles.tsx
*/

import { StyleSx } from "../../@types";

export const stylePaper: StyleSx = { width: "100%", borderRadius: 0 };

export const styleText: StyleSx = { flexGrow: 1 };
/*
    src\components\menu\index.tsx
*/

export * from "@mui/material";

E por fim o Footer:

/*
  src\components\footer\footer.tsx
*/

import { Box } from "@mui/material";
import packageData from "../../../package.json";

export const Footer = () => {
    return (
        <Box textAlign="center" padding={2}>
            MITH - Magna Innovation Technology Hub - Projeto Referência{" "}
            {packageData?.version}
        </Box>
    );
};
/*
    src\components\footer\footer.tsx
*/

export * from "./footer";

Vamos ajustar o aquivo de layout para receber o Navbar, Menu e Footer:

/*
    src/layouts/layoutMain.tsx
*/

import { Box, Grid, Stack, Paper } from "@mui/material";
import { Footer } from "../components/footer";
import { Menu } from "../components/menu/menu";
import { Navbar } from "../components/navbar";
import { Props } from "./@types";
import { useLayout } from "./useLayout";

export const LayoutMain = ({ children }: Props) => {
    const { open, handleToggleMenu } = useLayout();

    return (
        <>
            <Stack minHeight={"100vh"}>
                <Box>
                    <Navbar handleToggleMenu={handleToggleMenu} />
                </Box>
                <Stack flexGrow={1} component={Paper}>
                    <Grid container flexGrow={1}>
                        <Grid
                            item
                            xs={12}
                            md={3}
                            display={open ? "block" : "none"}
                            sx={{ transition: "all 1s ease" }}
                        >
                            <Menu />
                        </Grid>
                        <Grid item flexGrow={1}>
                            {children}
                        </Grid>
                    </Grid>
                </Stack>
                <Stack component={Paper}>
                    <Footer />
                </Stack>
            </Stack>
        </>
    );
};

A saída deverá ser como esse abaixo:

layout main

Theme e Colors

A seleção de cores e temas é de extrema importância no design de sistemas, permitindo personalizar a aparência de acordo com as preferências do cliente. Normalmente, as cores são fornecidas em conjunto com o esquema de template criado por profissionais de UX ou UI. Além disso, é fundamental considerar as cores de mensagens, alertas e erros do sistema, garantindo uma experiência coesa.

Os temas desempenham um papel significativo ao combinar cores, tipografia e ícones para criar uma aparência visualmente atraente e consistente. Dependendo da finalidade do sistema, é possível optar por temas que atendam a necessidades específicas, como um tema escuro para ambientes com baixa luminosidade ou um tema vibrante para transmitir energia. Ao receber as cores e o esquema template, é importante implementar essas escolhas de forma global no sistema. No caso de estarmos utilizando o Material UI, podemos aproveitar o mecanismo de gerenciamento de temas disponibilizado por essa biblioteca.

Design tokens

Design tokens são componentes essenciais na criação de sistemas de design. Eles são valores individuais, como cores, tipografia, espaçamento e outros atributos de design, que são definidos de forma centralizada e reutilizáveis em todo o sistema. Um exemplo de design token seria um token de cor primária, que define uma cor específica, como "#FF0000" para vermelho, e é utilizado em vários elementos do sistema, como botões, barras de navegação e ícones, garantindo uma aparência uniforme em todas as partes do design. Os design tokens proporcionam benefícios significativos, como consistência visual, facilidade de manutenção e escalabilidade, permitindo atualizações rápidas e eficientes em todo o sistema.

/*
    src\theme\colors.tsx
*/

export const colors = {
    primary: {
        main: "#1976D2", // Azul principal
        light: "#63a4ff", // Azul claro
        dark: "#004ba0", // Azul escuro
        contrastText: "#fff", // Texto em contraste com a cor principal
    },
    secondary: {
        main: "#FF9800", // Laranja principal
        light: "#ffbe52", // Laranja claro
        dark: "#c66900", // Laranja escuro
        contrastText: "#fff", // Texto em contraste com a cor principal
    },
    darkMode: {
        main: "#121212", // Cor principal do modo escuro
        light: "#333333", // Cor clara do modo escuro
        dark: "#000000", // Cor escura do modo escuro
        contrastText: "#fff", // Texto em contraste com a cor principal do modo escuro
    },
};
/*
    src\theme\theme.tsx
*/

import { createTheme } from "@mui/material/styles";
import { colors } from "./colors";

export const theme = (fontSize: number = 16) => {
    return createTheme({
        palette: {
            mode: "light",
            primary: {
                main: colors.primary.main,
                light: colors.primary.light,
                dark: colors.primary.dark,
                contrastText: colors.primary.contrastText,
            },
            secondary: {
                main: colors.secondary.main,
                light: colors.secondary.light,
                dark: colors.secondary.dark,
                contrastText: colors.secondary.contrastText,
            },
        },
        typography: {
            fontSize: fontSize,
        },
    });
};
/*
    src\theme\themeDark.tsx
*/

import { createTheme } from "@mui/material/styles";
import { colors } from "./colors";

export const themeDark = (fontSize: number = 16) => {
    return createTheme({
        palette: {
            mode: "dark",
            background: {
                paper: colors.darkMode.light,
            },
            primary: {
                main: colors.darkMode.main,
                light: colors.darkMode.light,
                dark: colors.darkMode.dark,
                contrastText: colors.primary.contrastText,
            },
            secondary: {
                main: colors.secondary.main,
                light: colors.secondary.light,
                dark: colors.secondary.dark,
                contrastText: colors.secondary.contrastText,
            },
        },
        typography: {
            fontSize: fontSize,
        },
    });
};
/*
    src\theme\index.tsx
*/

export * from "./colors";
export * from "./theme";
export * from "./themeDark";

Dark mode - Tema escuro

Em nosso projeto de referência, implementaremos o tema escuro com o gerenciamento de tema do usuário utilizando o localStorage. Em alguns sistemas, esse gerenciamento pode ser obtido a partir do perfil do usuário ou do login, sendo gerenciado pelo backend.

Abaixo vamos criar uma função para capturar o tema do local storage.

/*
    src\helpers\getStorageTheme.tsx
*/

export const getStorageTheme = () => {
    const storedValue = localStorage.getItem("themeMode");
    return storedValue === null ? "light" : (storedValue as "light" | "dark");
};

Agora vamos criar um storage no zustand para amarzenar e gerenciar o thema de maneira global.

/*
    src\storeds\useThemeMode\useThemeMode.tsx
*/

import { create } from "zustand";
import { Props } from "./@types";
import { getStorageTheme } from "../../helpers/getStorageTheme";

export const useThemeMode = create<Props>((set) => ({
    themeMode: getStorageTheme(),

    toggleTheme: () =>
        set((state) => {
            const themeMode = state?.themeMode == "light" ? "dark" : "light";
            localStorage?.setItem("themeMode", themeMode);
            return { themeMode: themeMode };
        }),
}));
/*
    src\storeds\useThemeMode\@types.tsx
*/

export type Props = {
    themeMode: "light" | "dark";
    toggleTheme: () => void;
};
/*
    src\storeds\useThemeMode\index.tsx
*/

export * from "./useThemeMode";

Precisamos criar um provider para os themas

/*
    src\theme\providerTheme.tsx
*/

import { ThemeProvider } from "@mui/material/styles";
import { ReactNode } from "react";
import { useThemeMode } from "../storeds/useThemeMode";
import { theme, themeDark } from "./";

type Props = {
    children: ReactNode | ReactNode[];
};

export const ProviderTheme = ({ children }: Props) => {
    const { themeMode } = useThemeMode();

    return (
        <>
            <ThemeProvider theme={themeMode == "dark" ? themeDark() : theme()}>
                {children}
            </ThemeProvider>
        </>
    );
};

Atualize o App.tsx com o provider

/*
    src\App.tsx
*/

import { LayoutMain } from "./layouts/layoutMain";
import { CssBaseline } from "@mui/material";
import { ProviderTheme } from "./theme/providerTheme";

export const App = () => {
    return (
        <>
            <CssBaseline />
            <ProviderTheme>
                <LayoutMain>
                    <h1>Conteúdo interno</h1>
                </LayoutMain>
            </ProviderTheme>
        </>
    );
};

Vamos criar um componente que tera o botão para alternar o thema e incluir no Navbar.

/*
    src\components\buttons\buttonsTheme\buttonsTheme.tsx
*/

import DarkModeIcon from "@mui/icons-material/DarkMode";
import LightModeIcon from "@mui/icons-material/LightMode";
import { IconButton } from "@mui/material";
import { styleButtonTheme } from "./styles";
import { useThemeMode } from "../../../storeds/useThemeMode";

export const ButtonsTheme = () => {
    const { themeMode, toggleTheme } = useThemeMode();
    return (
        <>
            {themeMode == "light" ? (
                <IconButton aria-label="theme" onClick={toggleTheme}>
                    <DarkModeIcon sx={styleButtonTheme} />
                </IconButton>
            ) : (
                <IconButton aria-label="theme" onClick={toggleTheme}>
                    <LightModeIcon sx={styleButtonTheme} />
                </IconButton>
            )}
        </>
    );
};
/*
    src\components\buttons\buttonsTheme\styles.tsx
*/

import { StyleSx } from "../../../@types";
import { colors } from "../../../theme";
import { getStorageTheme } from "../../../helpers/getStorageTheme";

export const styleButtonTheme: StyleSx = {
    color:
        getStorageTheme() == "light"
            ? colors?.primary?.contrastText
            : colors?.darkMode?.contrastText,
};
/*
    src\components\buttons\buttonsTheme\index.tsx
*/

export * from "./buttonsTheme";

Execute o projeto e você deverá ter uma saída como essas:

Theme Light

Theme Dark

Observação: no projeto de referência você terá um exemplo com o aumento e redução de fonte.

States Globais

As States Globais no React são uma das ferramentas mais poderosas e úteis para o gerenciamento de estado em aplicações web. Elas foram introduzidas pela primeira vez no React em 2018, com a versão 16.3. Desde então, tornaram-se uma parte essencial do fluxo de trabalho de desenvolvedores React. Existem várias bibliotecas populares para gerenciamento de estado global no React, como Redux, Context API e Recoil.

Uma State Global, ou estado global, permite armazenar dados que podem ser acessados e atualizados de qualquer componente em uma aplicação React. Isso elimina a necessidade de passar props entre componentes pai e filho, simplificando o desenvolvimento e a manutenção do código.

A mais popular delas é o Redux que foi lançado em 2015 e desde então tem sido amplamente adotado pela comunidade React. Ele utiliza um padrão de fluxo unidirecional de dados, onde todas as atualizações de estado são feitas através de ações que são despachadas e manipuladas por reducers. Durante o desenvolvimento devemos ter cuidado na forma que implementamos o Redux, pois muitas vezes adicionamos uma camada de complexidade desnecessária e a centralização de States pode tornar um grande problema na evolução ou manutenção da aplicação.

Outras bibliotecas e padrões também surgiram ao longo dos anos para gerenciamento de estado global no React. O Context API é uma das principais adições ao React desde a versão 16.3 e oferece uma forma nativa de criar e compartilhar estados globais entre componentes. Ele é bastante utilizado em combinação com o Hook useContext.

No entanto, uma opção interessante e cada vez mais adotada é o Zustand. O Zustand é uma biblioteca de gerenciamento de estado global leve e minimalista para o React. Ele foi criado com o objetivo de oferecer uma solução simples e intuitiva para o gerenciamento de estado, sem a necessidade de conceitos complexos ou sintaxes verbosas.

Uma das principais razões para escolher o Zustand é sua abordagem funcional e declarativa. Ele utiliza hooks do React para definir e manipular estados globais, o que torna o código mais legível e fácil de entender. Além disso, o Zustand oferece uma API simples e intuitiva, permitindo que os desenvolvedores definam estados e ações de forma concisa e direta sem necessidade de criação de contextos.

Além disso, o Zustand possui uma integração perfeita com o ecossistema do React. Ele funciona muito bem com hooks e pode ser facilmente combinado com outras bibliotecas e ferramentas populares, como React Router e React DevTools.

Em resumo, o Zustand é uma opção atraente para o gerenciamento de estado global no React. Sua abordagem funcional, declarativa e leve, juntamente com sua integração perfeita com o ecossistema do React, fazem dele uma escolha certa para nossas aplicações React.

Exemplo de um State Global que armazena dados do Usuário:

import create from "zustand";

type User = {
    name: string;
};

type UserStore = {
    user: User | null;
    setUser: (user: User) => void;
    clearUser: () => void;
};

const useUserStore = create<UserStore>((set) => ({
    user: null,
    setUser: (user) => set(() => ({ user })),
    clearUser: () => set(() => ({ user: null })),
}));

Utilizando em um componente:

import useUserStore from "./useUserStore";

function UserProfile() {
    const { user, setUser, clearUser } = useUserStore();

    if (!user) {
        return (
            <div>
                <p>Nenhum usuário logado.</p>
                <button onClick={() => setUser({ name: "John" })}>
                    Fazer login
                </button>
            </div>
        );
    }

    return (
        <div>
            <p>Usuário: {user.name}</p>
            <button onClick={clearUser}>Logout</button>
        </div>
    );
}

Esse código é apenas um exemplo para demostrar a ferramenta, não recomendamos a utilização do mesmo.

Hooks

Com a evolução do React para Componentes Funcionais, surgiu a necessidade de ter uma forma de guardar, manipular e observar os estados (states). Para atender a essa necessidade, foram criados os Hooks ⧉.

O React possui uma documentação muito rica sobre o assunto e é sempre importante acompanhar a documentação oficial. Recentemente, foram disponibilizados novos Hooks que resolvem diversos paradigmas.

Hook Personalizados

Podemos criar Hooks personalizados para nossa aplicação, e uma das vantagens é a separação da lógica dos componentes do próprio componente. Além disso, existe a possibilidade de reutilização da lógica e, com o desacoplamento da lógica e do componente, temos uma manutenção simplificada.

Em nosso projeto de referência, estamos utilizando o design pattern de custom hook e aproveitando todas as vantagens que ele oferece.

Helpers

Em nossos projetos, muitas vezes precisamos criar funções para facilitar trechos de código ou para reutilizá-las em diferentes partes do código. Essas funções, que resolvem desde situações simples até as mais complexas, são categorizadas como Helpers ou Utils. Para manter um padrão consistente, agrupamos essas funções em uma pasta chamada helpers.

Manter uma convenção de nomenclatura consistente em um projeto é fundamental para garantir a organização, legibilidade e facilidade de manutenção do código. Em nossos projetos React JS, seguimos a convenção de nomenclatura camelCase para nomear funções e componentes.

No nosso projeto, a convenção para nomear funções é seguir o padrão handle + verbo de ação, o que facilita a identificação e manutenção do código. Além disso, as funções devem ser organizadas de acordo com o escopo a que pertencem e armazenadas na pasta helpers quando são de escopo global, dentro da raiz do projeto, ou na pasta do component ou page quando têm um escopo mais restrito. Dessa forma, garantimos uma nomenclatura consistente e uma organização adequada no projeto, especialmente em relação às funções utilizadas no desenvolvimento.

É crucial que todos os desenvolvedores do projeto sigam essa convenção de nomenclatura, para garantir a consistência e facilidade de manutenção do código. Se você encontrar algum arquivo ou função que não esteja seguindo essa convenção, por favor, renomeie-o ou entre em contato com o responsável pelo projeto para garantirmos a consistência do código.

export const getStorageTheme = () => {
    const storedValue = localStorage.getItem("themeMode");
    return storedValue === null ? "light" : (storedValue as "light" | "dark");
};

Types

Atualmente, é muito comum criar projetos profissionais utilizando TypeScript, e seguimos essa prática em nossos projetos. Sempre preferimos utilizar types em vez de interfaces para padronizar as tipagens em nossos projetos em React JS.

Como nossos projetos em React JS são desenvolvidos com base em componentes funcionais, as types se adaptam melhor para a tipagem nesse contexto. Por outro lado, as interfaces são mais aproveitadas na implementação de classes.

Ao utilizar types, podemos definir a estrutura dos dados de forma mais flexível, permitindo a criação de tipos complexos e uniões de tipos, bem como a utilização de recursos como tipos genéricos. Além disso, a sintaxe de types é mais concisa e expressiva, tornando a leitura e a manutenção do código mais eficientes.

No entanto, é importante ressaltar que a escolha entre types e interfaces depende do contexto e das necessidades específicas de cada projeto. Ambos são recursos poderosos do TypeScript e devem ser utilizados de acordo com as melhores práticas e convenções adotadas pela equipe de desenvolvimento.

Em nossos projetos, adotamos a preferência por types para padronizar as tipagens, garantindo consistência e facilitando a manutenção do código em toda a base do projeto. Essa abordagem nos ajuda a escrever código mais seguro, com menos erros e melhor escalabilidade.

Dessa forma, ao utilizar TypeScript em nossos projetos React JS, optamos por utilizar types como a principal forma de definir as tipagens, aproveitando ao máximo os recursos dessa linguagem estática e promovendo a qualidade do código em nossos projetos.

Routes

As rotas desempenham um papel fundamental no desenvolvimento de aplicações front-end, e no contexto do React JS, uma das melhores opções para lidar com rotas é a biblioteca React Router Dom. Essa biblioteca é amplamente reconhecida e possui uma excelente documentação, além de ser amplamente adotada pela comunidade React.

O React Router Dom fornece uma solução completa e flexível para o gerenciamento de rotas em aplicações React. Ele permite criar um sistema de roteamento robusto e dinâmico, permitindo a navegação entre diferentes páginas e componentes sem a necessidade de recarregar a página.

Uma das principais vantagens do React Router Dom é a sua integração perfeita com o ecossistema do React. Ele foi projetado para funcionar em harmonia com os componentes do React, permitindo que você defina rotas usando componentes JSX de maneira declarativa e intuitiva.

A biblioteca oferece uma variedade de recursos, como a definição de rotas aninhadas, o uso de parâmetros dinâmicos nas URLs, redirecionamentos, proteção de rotas autenticadas e muito mais. Além disso, o React Router Dom possui uma API flexível e extensível, permitindo que você personalize e estenda as funcionalidades de acordo com as necessidades do seu projeto.

Outra vantagem do React Router Dom é a sua ampla adoção pela comunidade React. Isso significa que você encontrará suporte e recursos abundantes, como tutoriais, exemplos e plugins, que podem ajudar no desenvolvimento e solução de problemas relacionados às rotas em sua aplicação.

Em resumo, o React Router Dom é uma escolha sólida para o gerenciamento de rotas em aplicações React. Com sua documentação abrangente, integração perfeita com o React e recursos avançados, ele permite que você crie um sistema de roteamento flexível e poderoso para sua aplicação. Portanto, recomendamos a utilização do React Router Dom em nossos projetos, aproveitando todos os benefícios que essa biblioteca traz para o desenvolvimento de aplicações com rotas no React JS.

Criando rotas

O gerenciamento de rotas em um projeto muitas vezes é subestimado pelos desenvolvedores, o que pode resultar em problemas e conflitos. É comum que dois ou mais desenvolvedores enviem atualizações simultâneas e acabem excluindo uma ou mais rotas.

No nosso projeto de referência, criamos uma forma simples e eficaz de gerenciar rotas, praticamente eliminando conflitos e evitando a criação de um único arquivo de rotas gigantesco.

No projeto de referência, está disponível um Custom Hook que realiza o gerenciamento de rotas. Nele, temos dois componentes: um Wrapper, que apenas encapsula o conteúdo para gerenciamento de espaços (você pode personalizá-lo para o seu projeto), e uma página de Error404. No projeto de referência, usamos a mesma página de erro para rotas não encontradas e acesso negado por falta de permissão, mas você também pode personalizá-la de acordo com o seu projeto.

/* 
    src\routes\useRouter.tsx
*/

import { RouteObject, createBrowserRouter } from "react-router-dom";
import { Wrapper } from "../components/wrapper";
import { Error404 } from "../pages/error404";
import { Routes } from "../@types";
import { LayoutMain } from "../layouts";

const ROUTES: Record<string, { routes: Function }> = import.meta.glob(
    "/src/pages/**/routes.tsx",
    {
        eager: true,
    }
);

export const useRouter = () => {
    const pageRoutes: RouteObject[] = [];
    const loadRoutes = async () => {
        for (const path in ROUTES) {
            const routes = ROUTES[path].routes();
            pageRoutes.push(
                ...routes.map((item: Routes) => {
                    const Element = item?.element;
                    return {
                        path: item?.path,
                        element: (
                            <LayoutMain>
                                <Wrapper>
                                    <Element />
                                </Wrapper>
                            </LayoutMain>
                        ),
                    };
                })
            );
        }
    };

    loadRoutes();

    const router = createBrowserRouter([
        ...pageRoutes,
        {
            path: "*",
            element: (
                <LayoutMain>
                    <Wrapper>
                        <Error404 />
                    </Wrapper>
                </LayoutMain>
            ),
        },
    ]);

    return { router };
};

Observe que nosso arquivo faz uma importação dinâmica usando a função import.meta.glob, importando todos os arquivos routes.tsx das pastas dentro da pasta pages.

//
// src/pages/*/routes.tsx
// Este arquivo será usado para cadastrar rotas em cada uma das
// pastas dentro de "pages"
//

import { Home } from "./";
import { Routes } from "../../@types";

export const routes = (): Routes[] => {
    return [
        {
            path: "/",
            element: Home,
        },
    ];
};

Por fim, precisamos atualizar o arquivo App.tsx:

/*
    src/App.tsx
*/

import { CssBaseline } from "@mui/material";
import { RouterProvider } from "react-router-dom";
import { useRouter } from "./routes";
import { ProviderTheme } from "./theme/providerTheme";

export const App = () => {
    const { router } = useRouter();

    return (
        <>
            <CssBaseline />
            <ProviderTheme>
                <RouterProvider router={router} />
            </ProviderTheme>
        </>
    );
};

Formulários

Os formulários desempenham um papel fundamental em qualquer aplicação, seja ela web ou em qualquer outra plataforma. E para facilitar o processo de desenvolvimento e padronizar os campos de formulários em nossos projetos React JS, vamos utilizar a biblioteca React Hook Form.

No nosso projeto de referência, criamos algumas abstrações para ajudar no pontapé inicial do projeto. A criação de formulários é uma etapa crucial, e é importante simplificar o processo de desenvolvimento.

Neste capítulo, vamos explorar e explicar como implementar formulários robustos de forma mais simples possível. Comece criando um clone do projeto de referência, pois isso já lhe proporcionará uma grande economia de tempo.

A documentação do React Hook Form será sua aliada nessa jornada. Ela oferece uma ampla gama de recursos e exemplos práticos para auxiliá-lo na criação e validação de formulários. Além disso, a comunidade em torno do React Hook Form é ativa e está sempre disponível para ajudar.

Ao utilizar o React Hook Form, você poderá criar campos de formulários personalizados, definir validações específicas para cada campo, lidar com estados de erro e sucesso, entre outras funcionalidades. Isso permitirá que você construa formulários robustos e eficientes em seus novos projetos, seguindo as melhores práticas e evitando erros comuns.

Padronizar os campos de formulários em seus projetos trará diversos benefícios. Você terá um código mais limpo e organizado, facilitando a manutenção e o entendimento do mesmo. Além disso, ao seguir uma abordagem padronizada, você economizará tempo e esforço, pois não precisará reinventar a roda a cada novo formulário que precisar implementar.

Portanto, aproveite a documentação do React Hook Form e as abstrações do projeto de referência para agilizar o desenvolvimento de formulários em seus novos projetos. Ao utilizar essa biblioteca e seguir as melhores práticas, você estará construindo aplicações com formulários sólidos, eficientes e de fácil manutenção.

Comparando abstrações com o uso comum das bibliotecas:

A seguir faremos uma comparação entre o uso da abstração contida no projeto de referência e o uso sem abstrações.

Código utilizando as bibliotecas MUI e React Hook Form:

import { yupResolver } from "@hookform/resolvers/yup";
import { FormHelperText, Grid, TextField } from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import { maskCpf } from "../../helpers/maskCpf";
import { maskPhone } from "../../helpers/maskPhone";
import { schema } from "./validations";

export const ContactUs = () => {
    const {
        control,
        handleSubmit,
        formState: { errors },
    } = useForm({
        resolver: yupResolver(schema),
    });

    return (
        <form onSubmit={handleSubmit((data) => console.log(data))}>
            <Grid container spacing={2}>
                <Grid item xs={12} md={6}>
                    <Controller
                        name="nome"
                        control={control}
                        render={({ field: { onChange, value, ref } }) => (
                            <TextField
                                inputRef={ref}
                                label="nome"
                                value={value ?? ""}
                                onChange={onChange}
                            />
                        )}
                    />
                    <FormHelperText error={!!errors?.["nome"]?.message}>
                        <>{errors?.["nome"]?.message}</>
                    </FormHelperText>
                </Grid>
                <Grid item xs={12} md={6}>
                    <Controller
                        name="sobrenome"
                        control={control}
                        render={({ field: { onChange, value, ref } }) => (
                            <TextField
                                inputRef={ref}
                                label="Sobrenome"
                                value={value ?? ""}
                                onChange={onChange}
                            />
                        )}
                    />
                    <FormHelperText error={!!errors?.["sobrenome"]?.message}>
                        <>{errors?.["sobrenome"]?.message}</>
                    </FormHelperText>
                </Grid>
                <Grid item xs={12} md={6}>
                    <Controller
                        name="cpf"
                        control={control}
                        render={({ field: { onChange, value, ref } }) => (
                            <TextField
                                inputRef={ref}
                                label="CPF"
                                value={value ?? ""}
                                onChange={(e) =>
                                    onChange(maskCpf(e?.target?.value))
                                }
                            />
                        )}
                    />
                    <FormHelperText error={!!errors?.["sobrenome"]?.message}>
                        <>{errors?.["sobrenome"]?.message}</>
                    </FormHelperText>
                </Grid>
                <Grid item xs={12} md={6}>
                    <Controller
                        name="celular"
                        control={control}
                        render={({ field: { onChange, value, ref } }) => (
                            <TextField
                                inputRef={ref}
                                label="Celular"
                                value={value ?? ""}
                                onChange={(e) =>
                                    onChange(maskPhone(e?.target?.value))
                                }
                            />
                        )}
                    />
                    <FormHelperText error={!!errors?.["celular"]?.message}>
                        <>{errors?.["celular"]?.message}</>
                    </FormHelperText>
                </Grid>
                <Grid item xs={12} md={6}>
                    <Controller
                        name="email"
                        control={control}
                        render={({ field: { onChange, value, ref } }) => (
                            <TextField
                                inputRef={ref}
                                label="E-mail"
                                value={value ?? ""}
                                onChange={onChange}
                            />
                        )}
                    />
                    <FormHelperText error={!!errors?.["email"]?.message}>
                        <>{errors?.["email"]?.message}</>
                    </FormHelperText>
                </Grid>
            </Grid>
        </form>
    );
};

Observe que o código gerado se torna muito verboso, difícil de dar manutenção e é fácil se perder nele. Agora vamos ver o mesmo formulário, mas desta vez com a abstração do projeto de referência:

import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import { Form } from "../../components/form";
import { Input } from "../../components/form/input/input";
import { maskCpf } from "../../helpers/maskCpf";
import { maskPhone } from "../../helpers/maskPhone";
import { schema } from "./validations";

export const ContactUs = () => {
    const { control, handleSubmit } = useForm({
        resolver: yupResolver(schema),
    });

    return (
        <Form handleSubmit={handleSubmit((data) => console.log(data))}>
            <Input name="nome" label="Nome" control={control} col={6} />
            <Input
                name="sobrenome"
                label="Sobrenome"
                control={control}
                col={6}
            />
            <Input
                name="cpf"
                label="CPF"
                control={control}
                col={3}
                mask={maskCpf}
            />
            <Input
                name="celular"
                label="Celular"
                control={control}
                col={3}
                mask={maskPhone}
            />
            <Input name="email" label="E-mail" control={control} col={6} />
        </Form>
    );
};

Perceba que, enquanto o formulário comum supera as 100 linhas de código, nosso formulário, mesmo com quebras de linha, possui menos de 30 linhas. Temos um código limpo e fácil de entender.

Além disso, no projeto de referência, você encontrará abstrações feitas para outros tipos de inputs, como selectbox, checkbox, radio, entre outros. Essas abstrações proporcionam uma forma consistente e simplificada de lidar com diferentes tipos de entradas, promovendo maior reutilização de código e facilitando a manutenção a longo prazo.

Validações com YUP

No projeto de referência, optamos por utilizar uma das bibliotecas de validação mais poderosas disponíveis: o YUP. Sua versatilidade permite validar qualquer objeto de maneira simples, oferecendo uma ampla variedade de recursos.

Além disso, o YUP possui uma integração perfeita com o React Hook Form. No entanto, para aproveitar essa integração, é necessário instalar a biblioteca de integração entre os dois:

    npm install @hookform/resolvers

A seguir, demonstraremos algumas das validações possíveis com essa biblioteca. No entanto, vale ressaltar que você pode encontrar um vasto conteúdo sobre YUP na comunidade React e em fóruns especializados.

import * as yup from "yup";

// Validação simples
const schema = yup.object({
    pesoLiquido: yup.string().required("Campo obrigatório"),
    tipoPesquisa: yup.object({
        id: yup.number().required("Campo obrigatório"),
    }),
    number: yup.number().required("Campo obrigatório"),
    dataNascimento: yup.date().required("Campo obrigatório"),
    listProdutor: yup
        .array()
        .of(
            yup.object().shape({
                id: yup.number().required("Campo obrigatório"),
                description: yup.string().required("Campo obrigatório"),
            })
        )
        .min(1, "Informe no mínimo 1 produtor"),
});

// Validação condicional com campo true ou false
const schema = yup.object({
    quantidade: yup
        .number()
        .typeError("Informe um número")
        .positive()
        .integer()
        .when("flagPrincipal", {
            is: true,
            then: yup.number().min(65),
        }),
    flagPrincipal: yup.boolean(),
});

// Validação condicional com campo com função de teste
const schema = yup.object({
    quantidade: yup
        .number()
        .typeError("Informe um número")
        .positive()
        .integer()
        .when("idSituacao", {
            is: (idSituacao) => idSituacao == 101,
            then: yup.number().min(65),
        }),
    idSituacao: yup.number(),
});

// Validação condicional comparando datas entre dois campos
const schema = yup.object({
    inicioColheita: yup
        .date()
        .typeError("Data inválida")
        .required("Campo obrigatório")
        .test(
            "data",
            "Início não pode ser menor que fim da colheita",
            (data, fields) => {
                const compare = compareAsc(fields?.parent?.fimColheita, data!);

                return compare < 0 ? false : true;
            }
        ),
    fimColheita: yup
        .date()
        .typeError("Data inválida")
        .required("Campo obrigatório")
        .test(
            "data",
            "Fim não pode ser menor que inicio da colheita",
            (data, fields) => {
                const compare = compareAsc(
                    data!,
                    fields?.parent?.inicioColheita
                );

                return compare < 0 ? false : true;
            }
        ),
});

Boas Práticas de Desenvolvimento

Taxonomia

  • Nomeie as pastas de acordo com um padrão estabelecido: "Representação do tema" + "Verbo de ação".
  • Exemplo: "grupoConsultar" para consultar grupos ou "subgrupoCadastrar" para cadastrar subgrupos.
  • Nomeie os arquivos dos cards utilizando o título exibido na tela para facilitar a identificação.

if sem else

  • Utilizar a abordagem "if sem else" traz algumas vantagens, como:
    • Redução da complexidade do código.
    • Melhor legibilidade e entendimento.
    • Evita aninhamentos desnecessários.
    • Facilita a manutenção e o teste do código.

Nomeando Funções

  • Siga a convenção camelCase para nomear funções e componentes.
  • Utilize o padrão "handle" + "Verbo de ação" para nomear funções.
  • Organize as funções de acordo com a UC a qual pertencem, armazenando-as na pasta "helpers" dentro da pasta correspondente.
  • Mantenha consistência e siga essa convenção em todo o projeto.

Atenção com os Loops

  • Escolha a ferramenta correta para percorrer arrays, listas ou fazer repetições.
  • Utilize o map quando precisar transformar cada elemento de um array em outro valor.
  • Use o forEach para iterar sobre um array sem modificá-lo, quando precisar realizar ações específicas para cada elemento.
  • Considere o uso do for quando precisar controlar a condição de parada do loop e a atualização do contador.
// Exemplo com map
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map((number) => number * number);
console.log(squaredNumbers); // [1, 4, 9, 16, 25]

// Exemplo com forEach
const names = ["John", "Jane", "Mary"];
names.forEach((name) => console.log(`Hello, ${name}!`));
// Output: "Hello, John!" "Hello, Jane!" "Hello, Mary!"

// Exemplo com for
for (let i = 0; i < 10; i++) {
    console.log(i);
}

Avalie as características do seu loop e sua finalidade para escolher a abordagem adequada, mantendo o código limpo e correto.