Imagine que você deva construir um painel de administração de usuário para ativar e desativar usuários com uma interface de usuário simples que inclui uma tabela e um botão para alternar o status ativo de cada usuário. No entanto, nem todos os usuários gostam de tabelas. E se permitíssemos que os usuários alternassem dinamicamente entre o layout de tabela e grade? Embora isso possa adicionar código adicional ao aplicativo, também pode fornecer a satisfação do usuário, um objetivo principal de qualquer produto.

Neste artigo, construiremos um componente LayoutSwitch simples aproveitando o Padrão de componentes compostos de reação e uma pitada de API de contexto sabor para tornar nossas vidas mais fáceis.

Aqui está um GIF mostrando o componente LayoutSwitch concluído que construiremos no final desta postagem.

O layout muda de lista para grade clicando no botão

Primeiros passos

Idealmente, o componente LayoutSwitch faria parte de um aplicativo React existente. Para o bem desta postagem e experimentação, vamos criar um novo aplicativo React com o Criar React App padrão.

 # usando fio
yarn criar react-app react-layout-switch
# usando npx
npx create-react-app react-layout-switch

Alterne para o diretório do aplicativo e inicie o aplicativo React:

 # mudar para o diretório do aplicativo
CD react-layout-switch
# começar a usar fio
início do fio
# começar a usar npm
npm run start

Esta postagem se concentra principalmente na construção do componente LayoutSwitch ; não escreveremos nenhum estilo neste tutorial por uma questão de tempo, então substitua os estilos em App.css e index.css pelos estilos de este arquivo Gist .

Visão geral do componente

Abaixo está uma visão geral dos componentes que iremos construir.

Componentes básicos

  • App é o componente raiz (neste caso de uso, seria AdminDashboard ou algo semelhante)
  • UsersTable é o JSX para renderizar um layout de tabela
  • UsersGrid é o JSX para gerar um layout de grade

Mais layouts podem ser adicionados por lógica de negócios, como quadros suspensos do tipo Trello, onde os usuários podem se mover entre os estados ativo e inativo ou um sistema de gerenciamento de reclamações para controle de status aberto/em espera/fechado. Um layout de tabela ou grade pode fornecer uma visão geral rápida dos dados, enquanto um layout de placa pode fornecer mais controle. Não há limite para este caso de uso.

Componentes de controle de layout

  • LayoutSwitch é o componente pai que mantém o estado do layout e controla a renderização dos filhos
  • Options é um componente wrapper para botões de opção de layout
  • Botão é um botão individual para cada opção de layout
  • Conteúdo é um componente de wrapper para todos os componentes de layout, incluindo grades, tabelas e muito mais

Options e Content agrupam os respectivos componentes, dando mais controle sobre a lógica e os estilos de renderização.

Buscando dados e configuração inicial do layout

Configure o componente App que exibe a lista de usuários obtida em JSON Placeholder API .

Adicione o código abaixo ao App.jsx :

 import React from'react';
import {useFetch} de'./hooks/useFetch';
import'./App.scss'; function App () { const { dados: usuários, erro, carregando, }=useFetch ('/usuários'); if (erro) { retornar 

{error}

; } Retorna (

Usuários

{carregando &&

Carregando usuários...

} {usuários!==null? ( {/** Em breve... Tabela, grade e outros itens */} ): (

Ainda sem usuários

)}
); } exportar aplicativo padrão;

useFetch é um Gancho personalizado simples que pega um ponto de extremidade e usa o Fetch API para obter e retornar um objeto com os valores data , error e loading .

Crie um novo arquivo chamado useFetch.js em src/hooks e copie o código de este GitHub Gist . Como alternativa, você pode usar React Query , SWR ou qualquer outra preferência para buscar dados.

Agora que os dados dos usuários estão disponíveis, vamos adicionar UsersTable e UsersGrid para renderizar a lista de usuários nos respectivos layouts.

Primeiro, adicione UsersTable.jsx :

 import React from'react'; function UsersTable ({users}) { Retorna ( 
{users.map (({id, nome, nome de usuário, empresa})=> ( ))} {! users.length && ( )}
# Nome Empresa
{id}

{nome}

{company.name}

Nenhum usuário....
); } exportar UsersTable padrão;

Em seguida, adicione UsersGrid.jsx :

 import React from'react'; function UsersGrid ({users}) { Retorna ( 
{users.map (({id, name, username, company})=> (

{nome}

Empresa: {company.name}

))} {! users.length &&

Nenhum usuário....

}
); } exportar UsersGrid padrão;

Agora, atualize App.jsx para renderizar os dois componentes:

.....
importar UsersTable de'./UsersTable';
importar UsersGrid de'./UsersGrid'; ..... {usuários!==null? (     ): ( ......

Ambos os layouts serão renderizados um após o outro. Continuaremos e trabalharemos no LayoutSwitch ao lado para controlar o layout.

LayoutSwitch e seus filhos

Aproveitando o padrão de componentes compostos, a lógica de comutação pode ser abstraída para o componente dedicado, LayoutSwitch .

Vamos começar adicionando um estado para activeLayout que renderiza os filhos envolvidos neste componente raiz. activeLayout é inicializado com defaultLayout , que é o único prop ao lado dos filhos deste componente.

 import React, {useState} de'react'; function LayoutSwitch ({children, defaultLayout}) { const [activeLayout, setActiveLayout]=useState (defaultLayout); Retorna (  {crianças}  );
}

Para compartilhar este estado com componentes filho e permitir atualizações de estado de um filho, neste caso, o componente de botão, podemos usar a API de contexto do React.

Crie um LayoutContext e adicione activeLayout e setActiveLayout como o valor para o provedor.

 import React, {useState, createContext} de'react';
const LayoutContext=createContext (); function LayoutSwitch ({children, defaultLayout}) { const [activeLayout, setActiveLayout]=useState (defaultLayout); valor const={ activeLayout, setActiveLayout, }; Retorna (  {crianças}  );
} 

Usando o gancho React.useContext , podemos ler os dados do contexto em outros componentes:

 const context=useContext (LayoutContext);

Mas para componentes fora de LayoutSwitch , este contexto não estará disponível e não deve ser permitido. Então, vamos adicionar o gancho personalizado, useLayoutContext , para facilitar a leitura e gerar um erro quando for usado fora do componente do provedor raiz.

 function useLayoutContext () { const context=useContext (LayoutContext); if (! contexto) { lançar novo erro ( `Componentes que requerem LayoutContext devem ser filhos do componente LayoutSwitch` ); } contexto de retorno;
}

Por exemplo:

 # using useLayoutContext
const context=useLayoutContext ();

Agora que o componente básico está configurado, vamos adicionar o componente Conteúdo . Isso renderizará o componente filho, como Table ou Grid , que corresponde ao estado activeLayout .

UsersTable e UsersGrid serão filhos, e cada filho tem um layout apropriado para permitir o componente Conteúdo compare com o estado e renderize um correspondente.

O renderizador Content decide qual componente de layout, a Table ou Grid , renderizar para um determinado estado de layout ativo.

 função Conteúdo ({crianças}) { const {activeLayout}=useLayoutContext (); Retorna (  {children.map (child=> { if (child.props.activeLayout!==activeLayout) retorna nulo; filho de retorno; })}  );
}

Agora, temos o conteúdo e o estado para armazenar activeLayout . Mas como podemos realmente alternar entre layouts?

Para fazer isso, adicione o componente Botão , que obtém o setActiveLayout para o layout do contexto e atualiza o estado de acordo.

 function Button ({children, layoutPreference, title}) { const {activeLayout, setActiveLayout}=useLayoutContext (); Retorna (  setActiveLayout (layoutPreference)} título={título} > {crianças}  );
}

Também podemos adicionar o componente Opções para fins de estilização e para obter mais controle sobre os botões.

 opções de função ({crianças}) { Retorna ( 
{crianças}
); }

Adicionamos todos os componentes necessários e tudo parece bem. Mas pode ser melhor.

LayoutSwitch deve ser o único responsável por renderizar e controlar os componentes relacionados ao layout. Qualquer coisa não relacionada quebraria a IU ou o próprio componente.

Então, vamos usar React isValidElement e child.type.name para garantir o não relacionado componentes e elementos não são renderizados com o LayoutSwitch e seus filhos.

Então, para fazer isso, devemos iterar nos filhos e validar cada filho. Se for um elemento válido, renderize-o. Caso contrário, ignore ou gere um erro dizendo que não é permitido.

Para LayoutSwitch , apenas os componentes Options e Content podem ser permitidos como filhos.

 function LayoutSwitch ({children,...}) { .... Retorna (  {children.map (child=> { if (! React.isValidElement (filho)) retorna nulo; if (! [Options.name, Content.name].includes (child.type.name)) { lançar novo erro ( `$ { child.type.name || child.type } não pode ser renderizado dentro de LayoutSwitch Os componentes válidos são [$ {Options.name}, $ {Content.name}] ` ); } filho de retorno; })}  ); ....
}

Vamos atribuir poderes semelhantes ao componente Opções também. Apenas Button é permitido.

 opções de função ({crianças}) { Retorna ( 
{children.map (child=> { if (! React.isValidElement (filho)) retorna nulo; if (child.type.name!==Button.name) { lançar novo erro ( `$ { child.type.name || child.type } não pode ser renderizado dentro de LayoutSwitch.Options Os componentes válidos são [$ {Button.name}] ` ); } filho de retorno; })}
); }

Component.name

Como uma observação rápida, não use o nome do componente como uma string para uma verificação de igualdade com child.type.name .

 # NÃO FAÇA ISSO
child.type.name==="Opções"

Quando o código é reduzido por meio de uglify/webpack , os nomes dos componentes não permanecem os mesmos na produção. Opções não aparecerá como “Opções”; em vez disso, ele aparecerá como qualquer caractere único, como y ou t neste exemplo.

Agora, quando lemos o nome do componente e comparamos child.type.name e Component.name , sempre resulta no mesmo valor para o respectivo filho e componente.

 child.type.name===Options.name
# y===y ou t===t ou qualquer===qualquer

Exportando LayoutSwitch e componentes compostos

Podemos exportar todos os componentes individualmente e importar cada um deles.

 # exportar individualmente
exportar {LayoutSwitch, Opções, Botão, Conteúdo}; # uso
import {LayoutSwitch, Options, Button, Content} de'./LayoutSwitch';
 ...  ... 

Outra maneira simples de fazer isso é criar um namespace para todos os componentes em LayoutSwitch e importar um componente.

 # OU, exportar em um único namespace
LayoutSwitch.Button=Botão;
LayoutSwitch.Options=Opções;
LayoutSwitch.Content=Content; exportar LayoutSwitch padrão; # uso
importar LayoutSwitch de'./LayoutSwitch';
 ...  ... 

Depende totalmente de você exportar e importar.

Com todos os componentes de que precisamos escritos, podemos conectar as coisas.

Concluindo

É hora de reunir o LayoutSwitch e seus componentes compostos filhos. Recomendo ter todas as opções como Object.freeze constante para que o filho ou qualquer fator externo não possa alterar o objeto de opções de layout. O componente App atualizado deve ser semelhante ao código abaixo:

...
import {LayoutSwitch, Options, Button, Content} de'./LayoutSwitch';
importar {BsTable, BsGridFill} de'react-icons/bs'; const LAYOUT_OPTIONS=Object.freeze ({tabela:'tabela', grade:'grade'}); function App () { Retorna ( ..... {usuários!==null? (               ): ( ...... )
}

Para manter a opção selecionada pelo usuário, utilize localStorage . Embora isso esteja fora do escopo deste tutorial, você pode explorar e persistir de acordo com sua preferência.

E isso é tudo por agora! Você pode encontrar o código completo em meu repositório GitHub e pode acessar o layout final desta demonstração para ver como os usuários podem mudar de um layout de tabela para o de grade.

Obrigado por ler. Espero que você tenha achado este post útil e, por favor, compartilhe-o com aqueles que podem se beneficiar dele. Ciao!

A postagem Convertendo tabelas em grades com o composto React componentes apareceu primeiro no LogRocket Blog .