DEV Community

Cover image for Componente Composto com React
Raul Cesar
Raul Cesar

Posted on

Componente Composto com React

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.

Surprise

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.

Card do exercicio

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

Raiz do componente

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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;
`;
Enter fullscreen mode Exit fullscreen mode

props.ts

import React from "react";

export interface CardProps {
  Avatar?: React.ReactNode;
  children: React.ReactNode;
}
export interface CardPropsContext {
  isOpen: boolean;
  handleClose: () => void;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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;
  }
`;
Enter fullscreen mode Exit fullscreen mode

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> : <></>;
};
Enter fullscreen mode Exit fullscreen mode

style.tsx

import styled from "styled-components";

export const CardBodyStyle = styled.div`
  display: flex;
  flex-direction: column;
  background: #e1e1e180;
  width: 100%;
  min-height: 40px;
`;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Acho que neste ponto, voçês tem algo parecido com isso.

Componente fechado

Componente aberto

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.

Exemplos

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)

Collapse
 
dionisio28 profile image
Dionísio Malteso

Gostei desse patern vou adotar em meus novos projetos.
Obrigado por compartilhar conhecimento!