Componente Composto com React
Tags: typescript, reactnative, react
Published: 2022-11-12 22:36:55

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pngdhnyrbo6930xf4mm6.gif)

##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](https://pt-br.reactjs.org/https://pt-br.reactjs.org/), Typescript, [Styled-components](https://pt-br.reactjs.org/) e [React-icons](https://react-icons.github.io/react-icons/icons?name=ai), 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](https://www.figma.com/file/ljHJjXd3mPaq6YoFhpw881/Composed-Card?node-id=0%3A1), caso queiram ter uma base do que seguir, também adicionei alguns desafios e features para vcs desenvolverem e praticarem.

![Card do exercicio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hssmg9xtgfs4sfn1hsfi.png)


##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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0sexy893z012oife56c.png)

##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.

![Componente fechado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8oivdndmgx0wpkfksyb9.png)

![Componente aberto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wav0c2xcs3yvuw0e6rua.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/os6vrquz853yur2hdge0.png)

##Finalizando
Galera, agradeço a leitura deste post, espero ter ajudado este é o [Codepen prometido](https://codesandbox.io/s/compose-d3j2m5?file=/src/App.tsx:8682-8977), caso tenham duvidas podem me chama no [Linkedin](https://www.linkedin.com/feed/) que respondo sempre que puder e me sigam no [Github](https://github.com/raulcesar54)!, logo mais terá um projeto open source bacana lá!! Até mais e bons códigos pra vcs!!!