Olá, me chamo Raul, atualmente sou dev front-end sênior e quero compartilhar com voces um pattern muito bacana, que venho adotando em meus projeto e podem deixar seus componentes muito mais reutilizáveis e escalaveis.
Introdução
O pattern é denominnado composed components, é considerado um pattern avançado e existe uma infinidade de materias a cerca deste pattern na web, porém, neste post vou tentar explicar da maneira mais clara que conseguir para facilitar o entendimento.
Esse parttern consiste em construir componentes mais flexiveis e escalaveis, usando a Context Api para criar interações internas do componente, desta maneira, evitamos o prop drilling e não precisaremos criar estados fora do componente para gerenciar seu comportamento interno, isso ajuda a interação dos componentes filhos com seu componente pai e de quebra, podemos deixar seu componente infinitamente mais flexivel, esse parttern poder ser aplicado tanto em React Native quanto com React JS, neste post usarei o React JS.
Vamos iniciar o projeto
Para estre projeto, vou utilizar React, Typescript, Styled-components e React-icons, no fim deste post, vou anexar o codepen do projeto para vocês conseguirem testar e clonar.
O componente
Para demonstrar o pattern vou criar este componente, um card com uma ação de abrir e fechar, ao abrir o card aparecera uma informação que pode ser qualquer coisa, desde um texto até um componente complexo.
Criei esse Figma, caso queiram ter uma base do que seguir, também adicionei alguns desafios e features para vcs desenvolverem e praticarem.
Parte 1 - Organizando a casa
Meu primeiro passo foi criar a estrutura de pasta para armazenar meu componente, eu gosto de deixar o componente Pai na raiz da pasta do componente, este componente Pai vai ser o responsável por gerenciar o contexto e tambem será o cara que irei importar onde quero aplicar o componente.
Componente Pai > Card
Parte 2 - Criando o componente Card
Sempre que posso eu divido meus componentes em 3 arquivos, um de estilo, um de props (usando typescript) e um de lógica (onde aplico as regras de negócio do componente).
No index.tsx, temos a regra de negócio aplicada, criei um estado para gerenciar se os componentes filhos estão abertos ou fechados e criei uma função que tem o papel de inverter o valor do estado.
Também criei um contexto passando o valor do estado e a função citada acima, partindo deste contexto usei o Provider para englobar meus componentes filhos, a partir deste momento, meus componentes filhos tem acesso ao estado e a função criada anteriormente.
index.tsx
import { createContext, useState } from "react";
import { CardProps, CardPropsContext } from "./props";
import { CardContainer } from "./style";
export const CardContext = createContext({} as CardPropsContext);
export const Card = ({ children, ...props }: CardProps) => {
const [open, setOpen] = useState(false);
function handleClose() {
setOpen((e) => !e);
}
return (
<CardContext.Provider value={{ isOpen: open, handleClose: handleClose }}>
<CardContainer {...props}>{children}</CardContainer>
</CardContext.Provider>
);
};
styled.tsx
import styled from "styled-components";
export const CardContainer = styled.div`
display: flex;
align-items: center;
background-color: #f4f4f4;
padding: 8px;
justify-content: space-between;
border-radius: 4px;
flex-direction: column;
gap: 16px;
`;
props.ts
import React from "react";
export interface CardProps {
Avatar?: React.ReactNode;
children: React.ReactNode;
}
export interface CardPropsContext {
isOpen: boolean;
handleClose: () => void;
}
Parte 3 - Criando o componente Button
Neste componente vou buscar o contexto que criamos anteriormente usando o useContext do React.
Apartir deste momento podemos invocar a função e alterar o estado do componente Pai através do componente Filho, passando essa função para o onClick do button já funciona bem, simples neh?!
index.tsx
import { useContext } from "react";
import { CardContext } from "..";
import { Button } from "./style";
import { BsChevronDown, BsChevronUp } from "react-icons/bs";
export const CardButton = () => {
const context = useContext(CardContext);
return (
<Button onClick={context.handleClose}>
{context.isOpen ? <BsChevronUp /> : <BsChevronDown />}
</Button>
);
};
style.tsx
import styled from "styled-components";
export const Button = styled.button`
cursor: pointer;
border: 0;
width: 30px;
height: 30px;
border-radius: 100px;
&:hover {
background-color: #00000005;
}
`;
Parte 4 - Finalizando os componentes filhos
Neste ponto, a única parte que ficou pendente é fazer o componente de corpo aparecer e desaparecer ao clicar no botão, para isso, importei o contexto como feito no passo anterior e busco a prop isOpen
, para criar uma condicional e mostrar o children
do componente CardBody
caso isOpen
etiver com valor verdadeiro.
index.tsx
import { useContext } from "react";
import { CardContext } from "..";
import { CardBodyStyle } from "./style";
export const CardBody: React.FC = ({ children }) => {
const context = useContext(CardContext);
return context.isOpen ? <CardBodyStyle>{children}</CardBodyStyle> : <></>;
};
style.tsx
import styled from "styled-components";
export const CardBodyStyle = styled.div`
display: flex;
flex-direction: column;
background: #e1e1e180;
width: 100%;
min-height: 40px;
`;
Parte 5 - Voltando e finalizando componente pai
Vamos voltar no componente Pai e vamos trazer esses dois componentes que criamos para dentro dele, desta maneira, a forma de escrita e import
dos componentes fica mais claro e explicito.
index.tsx
import { createContext, useState } from "react";
import { CardButton } from "./cardButton";
import { CardBody } from "./cardBody";
import { CardProps, CardPropsContext } from "./props";
import { CardContainer } from "./style";
export const CardContext = createContext({} as CardPropsContext);
export const Card = ({ children, ...props }: CardProps) => {
const [open, setOpen] = useState(false);
function handleClose() {
setOpen((e) => !e);
}
return (
<CardContext.Provider value={{ isOpen: open, handleClose: handleClose }}>
<CardContainer {...props}>{children}</CardContainer>
</CardContext.Provider>
);
};
Card.Button = CardButton;
Card.Body = CardBody;
Parte 6 - Aplicando o componente
Está é a ultima parte abordada no post, finalizando este ponto teremos o componente pronto e o pattern aplicado, o resto fica a critério de vcs aplicarem o restante do layout, no codepen anexado ao fim terá todo o projeto desenvolvido.
No app.tsx
vou aplicar o componente e apartir deste momento temos uma infinidade de possibilidades, desde, remodelar aonde cada componente fica e editar seus estilos individualmente sem prejudicar seu funcionamento padrão e melhor, sem precisar criar estados desnecessários para gerenciar o comportamento do componente.
app.tsx
import { Card } from "./components/card";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Card>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
width: "100%",
gap: 16
}}
>
<div style={{ marginLeft: "auto" }}>
<Card.Button />
</div>
</div>
<Card.Body>
<h1>Teste</h1>
</Card.Body>
</Card>
</div>
);
}
Acho que neste ponto, voçês tem algo parecido com isso.
Gente apartir deste momento podemos pirar e montar uma infinidade de variações da aplicação deste componete, sem precisar criar milhares de condicionais e props no componente pai.
Este exemplo está presente no codepen, peço que acesse para testar mais a fundo e ver o código na pratica.
Finalizando
Galera, agradeço a leitura deste post, espero ter ajudado este é o Codepen prometido, caso tenham duvidas podem me chama no Linkedin que respondo sempre que puder e me sigam no Github!, logo mais terá um projeto open source bacana lá!! Até mais e bons códigos pra vcs!!!
Top comments (1)
Gostei desse patern vou adotar em meus novos projetos.
Obrigado por compartilhar conhecimento!