React: O que é, e como funciona um Compound Component?

Recentemente encontrei um desafio no trabalho: A ideia seria criar um componente Stepper, onde os métodos do componente precisavam ser compartilhados, e acessados de forma externa. A questão é como, e qual a forma mais efetiva? É aí que os Compound Com…


This content originally appeared on DEV Community and was authored by Adrian Knapp

Recentemente encontrei um desafio no trabalho: A ideia seria criar um componente Stepper, onde os métodos do componente precisavam ser compartilhados, e acessados de forma externa. A questão é como, e qual a forma mais efetiva? É aí que os Compound Components entram (mas eu só fui saber disso após tentar algumas outras coisas).

A ideia é ter um ou mais componentes que trabalham juntos para atingir um objetivo. Normalmente, um componente é o pai, e os outros são os filhos. Com o intuito de prover uma API mais flexível e expressiva.

Algo como o <select> e <option>:

<select>
  <option value="option1">label1</option>
  <option value="option2">label2</option>
  <option value="option3">label3</option>
</select>

Caso você tente usar um sem o outro, não irá funcionar. Agora vamos imaginar caso não tivéssemos uma API de compound components para trabalhar. (E lembre, isso é apenas HTML, não JSX).

<select options="option1:label1;option2:label2;option3:label3"></select>

Claro que há algumas outras formas de imaginar isso, mas enfim. E como você expressaria o atributo disabled em uma API como essa? As coisas complicam.

Já os compound components, disponibilizam uma boa forma de conectar e interagir entre componentes.

Outro ponto importante sobre esse conceito de "estado implícito": O <select>, implicitamente armazena o estado da opção selecionada e compartilha isso com seus childrens, sendo renderizado da forma correta dependendo desse estado. Mas isso está implícito e não conseguimos ter acesso em nosso HTML.

Em minhas tentativas de tentar criar o componente Stepper, minha primeira abordagem foi através de Render Props, onde o componente <Stepper>, disponibiliza propriedades para o children, permitindo manipular os steps.

const Stepper = ({ children, initial = 0 }) => {
  const [active, setActive] = useState(initial);

  const nextStep = () => {
    if (active < children().props.children.length - 1);
      setActive(active + 1);
  };

  const prevStep = () => {
    if (active > 0) setActive(active - 1);
  };

  return (
    <div data-cid="stepper">
      {
        children({
          nextStep,
          prevStep,
          setActive,
          active
        }).props.children[active]
      }
    </div>
  );
};

E esse seria um exemplo de uso:

const steps = ["Step 1", "Step 2", "Step 3"];

const App = () => {
    return (
        <Stepper>
          {({ nextStep, prevStep, active }) => (
            <>
              {steps.map((step) => (
                <div key={step} id="step">
                  <p>{step}</p>
                  <button onClick={prevStep}>Prev Step</button>
                  <button onClick={nextStep}>Next Step</button>
                </div>
              ))}
            </>
          )}
        </Stepper>
    );
}

Porém, ao pesquisar um pouco mais, encontrei alguns conteúdos, e o que mais me chamou atenção, foi um artigo do Kent C. Dodds , que explica e soluciona isso através da Context API do React, entregando os métodos via Hooks. Tornando esse o uso:

import React, {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useState
} from "react";
import CommonStep from "../CommonStep";

const StepperContext = createContext(null);

type StepperContextModel = {
  nextStep: () => void;
  prevStep: () => void;
  setActive: (index: number) => void;
  active: number;
};

type StepperProps = {
  children: JSX.Element[];
};

const Stepper = ({ children }: StepperProps) => {
  const [active, setActive] = useState(0);

  const nextStep = useCallback(() => {
    if (active < children.length - 1) setActive(active + 1);
  }, [active, children]);

  const prevStep = useCallback(() => {
    if (active > 0) setActive(active - 1);
  }, [active]);

  const value: StepperContextModel = useMemo(
    () => ({
      nextStep,
      prevStep,
      setActive,
      active
    }),
    [active, nextStep, prevStep]
  );

  return (
    <StepperContext.Provider value={value}>
      {children[active]}
    </StepperContext.Provider>
  );
};

export const useStepperContext = (): StepperContextModel => {
  const context = useContext(StepperContext);

  if (!context) {
    throw new Error(
      `Stepper compound components cannot be rendered outside the Stepper component`
    );
  }

  return context;
};

const Step = ({ children }) => {
  const { nextStep, prevStep, active } = useStepperContext();

  return (
    <CommonStep nextStep={nextStep} prevStep={prevStep} active={active}>
      {children}
    </CommonStep>
  );
};

Stepper.Step = Step;

export default Stepper;

E esse, um exemplo de uso:

const steps = ["Step 1", "Step 2", "Step 3"];

const App = () => {
    return (
        <Stepper>
            {steps.map((step) => (
                <Stepper.Step>
                    <p>{step}</p>
                </Stepper.Step>
            ))}
        </Stepper>
    );
}

Exemplo dos componentes em ação: https://codesandbox.io/s/stepper-function-og2bl1
E o repositório: https://github.com/AdrianKnapp/compound-components

Basicamente, funciona por um contexto criado com o React, que armazena o estado e os mecanismos para o atualizarmos. Sendo o <Stepper>, o componente responsável por prover o valor desse contexto para o resto da árvore de elementos.

Muito mais simples, não? Porém, cada alternativa tem suas vantagens e casos de uso. Tendo utilidade em muitos cenários, não só em um Stepper, por exemplo.

Espero que esse conteúdo ajude você a solucionar futuros problemas, e que gere novas ideias na hora de criar componentes, criando APIs mais expressivas e efetivas.

Esse artigo foi baseado e inspirado em um post do Kent C. Dodds, feito em seu blog. Acesse em: https://kentcdodds.com/blog/compound-components-with-react-hooks


This content originally appeared on DEV Community and was authored by Adrian Knapp


Print Share Comment Cite Upload Translate Updates
APA

Adrian Knapp | Sciencx (2023-02-26T22:57:35+00:00) React: O que é, e como funciona um Compound Component?. Retrieved from https://www.scien.cx/2023/02/26/react-o-que-e-e-como-funciona-um-compound-component/

MLA
" » React: O que é, e como funciona um Compound Component?." Adrian Knapp | Sciencx - Sunday February 26, 2023, https://www.scien.cx/2023/02/26/react-o-que-e-e-como-funciona-um-compound-component/
HARVARD
Adrian Knapp | Sciencx Sunday February 26, 2023 » React: O que é, e como funciona um Compound Component?., viewed ,<https://www.scien.cx/2023/02/26/react-o-que-e-e-como-funciona-um-compound-component/>
VANCOUVER
Adrian Knapp | Sciencx - » React: O que é, e como funciona um Compound Component?. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/02/26/react-o-que-e-e-como-funciona-um-compound-component/
CHICAGO
" » React: O que é, e como funciona um Compound Component?." Adrian Knapp | Sciencx - Accessed . https://www.scien.cx/2023/02/26/react-o-que-e-e-como-funciona-um-compound-component/
IEEE
" » React: O que é, e como funciona um Compound Component?." Adrian Knapp | Sciencx [Online]. Available: https://www.scien.cx/2023/02/26/react-o-que-e-e-como-funciona-um-compound-component/. [Accessed: ]
rf:citation
» React: O que é, e como funciona um Compound Component? | Adrian Knapp | Sciencx | https://www.scien.cx/2023/02/26/react-o-que-e-e-como-funciona-um-compound-component/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.