DEV Community

Cover image for Container & Presentational Pattern - React
Diego Souza
Diego Souza

Posted on • Edited on

Container & Presentational Pattern - React

Os padrões de design ou design patterns tem como objetivo trazer ao projeto organização, legibilidade e como o próprio nome diz, padrões.

Esse é o primeiro artigo de uma série de como fazer uso de design patterns em projetos, especificamente usando React. E para começar vamos considerar o pattern que chamamos de Container/Presentational Pattern.

Tanto o container quanto o presentational são componentes, mas a forma com que eles trabalham são diferentes e tem como principal objetivo separar as responsabilidades. Com esse pattern nós podemos separar a responsabilidade da visualização da responsabilidade da lógica da aplicação.

Caso ainda essa explicação esteja nebulosa em sua mente, vamos as considerações e exemplos.

Container

São componentes que em sua responsabilidade está em como os dados são apresentados na interface. A principal função dos container components é passar os dados para os presentational components.

Princípios

  1. De modo geral os container components renderizam apenas os presentational components que se relacionam com os dados que precisam ser apresentados por aquele componente;

  2. Além disso, geralmente também esses componentes não recebem estilos, visto que apenas passam os dados para os componentes que realmente farão a renderização da interface - os presentational components;

  3. O foco desse componente é no comportamento e funcionalidade da aplicação, como os métodos e lógicas;

  4. O gerenciamento de estados fica concentrado nesse componente, assim como também o fluxo de dados. Caso precise usar hooks, variáveis e método de contexto esse é o componente que irá fazer isso;

  5. Facilidade em usar e entender as dependências do componente, como outras bibliotecas.

Presentational

São componentes que em sua responsabilidade está em *o que * mostrar na interface. A principal função dos presentational components é mostrar os dados que são recebidos.

Princípios

  1. Os dados são passados do container para os presentational por meio de props;

  2. Esses dados não são modificados no presentational, apenas exibidos. Se precisar alterar os dados faça isso dentro do container components;

  3. É um stateless component. Isto é, ele não gerencia nenhum estado, ou se gerenciar, que seja algo muito pontual;

  4. Recebe os dados exclusivamente por meio de props do container.


Essa é uma imagem que representa esse design pattern, com um mínimo de lógica - um fetch de dados que é feito dentro do container e passado por props para dentro do presentational.

Fluxograma do Container/Presentational Component


Show me the code

Container



import { useState, useEffect } from 'react';
import { PresentationalComponent } from './PresentationalComponent';

const ContainerComponent = () => {
  const [data, setData] = useState([]);

  function handleFetch() {
    fetch("https://randomuser.me/api")
      .then((response) => response.json())
      .then((data) => setData(data));
  }

  useEffect(() => {
    handleFetch();
  }, []);

  return (
      <PresentationalComponent 
         {...data} 
         handleFetch={handleFetch} 
      />
   );
};


Enter fullscreen mode Exit fullscreen mode

Como mostra o código acima, a lógica, métodos e estados ficam dentro do container component. E os dados que precisam ser apresentados em tela se passa por props para dentro do presentational component, assim como também os métodos.

Presentational



const PresentationalComponent = ({ 
   name, 
   email, 
   location, 
   handleFetch 
}) => {
  return (
    <Profile>
      <Info>{name.first}</Info>
      <Info>{email}</Info>
      <Info>
        {location.city}, {location.state}
      </Info>
      <Button onClick={handleFetch}>Buscar</Button>
    </Profile>
  );
};



Enter fullscreen mode Exit fullscreen mode

E como mostra o código acima, o objetivo do componente é apenas exibir os dados e se possível praticamente mais nada deve ser de sua responsabilidade.



.
├── ...
├── components
    ├── ExampleComponent
        ├── container
            ├── index.tsx
        ├── presentational
            ├── index.tsx
        index.tsx


Enter fullscreen mode Exit fullscreen mode

Para extrair o máximo de vantagens desse design pattern procure utilizar a convenção de nomenclatura de pastas e de arquivos em todo o projeto. Fazer assim vai facilitar o entendimento da aplicação e ajudará na manutenção dos componentes.

Note que há um arquivo index na raiz da pasta do componente. Esse arquivo tem como objetivo apenas exportar o container. É esse arquivo index que podemos usar para chamar o container component na aplicação.



export { ContainerComponent as Container } from './container';


Enter fullscreen mode Exit fullscreen mode

Talvez seja redudante e a comunidade reclama da quantidade de arquivos que se cria nesse design pattern. Pode ser que esse seja um dos ônus de usar esse pattern. Mas particularmente, não me importo com isso.

Acredito que essa convenção é mais para o desenvolvedor se localizar dentro do mundo das pastas de um projeto enorme por meio da IDE.

O ideal é nunca exportar o presentational component em qualquer arquivo que não seja o container component no qual ele está relacionado.

Além disso, poderá criar testes unitários de forma mais fácil para os dois tipos de componentes. Para isso mantenha o componente de apresentação o mais simples possível focando na renderização de propriedades e chamadas de métodos. Assim, o arquivo vai ficar mais limpo e legível.

Pode ser que com o tempo o container comece a ficar grande ou complexo. Antes disso acontecer, estude utilizar hooks e contexto para fazer a busca (fetch) de dados da API ou outro gerenciamento de estado que possa ser utilizado em toda a aplicação.

Dessa forma também, o container ficará mais limpo e legível.

Exemplo com hooks

Hooks



import { useState, useEffect } from 'react';

const useFetchUser = () => {
  const [data, setData] = useState([]);

  function handleFetch() {
    fetch("https://randomuser.me/api")
      .then((response) => response.json())
      .then((data) => setData(data));
  }

  useEffect(() => {
    handleFetch();
  }, []);

  return {
    handleFetch,
    data
  }
};


Enter fullscreen mode Exit fullscreen mode

Container



import useFetchUser from '@hooks/useFetchUser';
import { 
   PresentationalComponent 
} from './PresentationalComponent';

const ContainerComponent = () => {
  const { data, handleFetch } = useFetchUser();

  return (
      <PresentationalComponent 
         {...data} 
         handleFetch={handleFetch} 
      />
   );
};


Enter fullscreen mode Exit fullscreen mode

O arquivo ficou menor e o presentational component permanece da mesma forma.

Minhas considerações

Esse design pattern pode ser substituído por muitos outros, diria eu, até melhores e mais fáceis de trabalhar visando os mesmos princípios explicados e mostrados aqui. Porém, esse artigo foi criado não com o objetivo de incentivar o uso desse pattern, mas por razões acadêmicas e seculares.

Pode ser que você encontre um projeto assim em seu trabalho e provavelmente terá que seguir esses padrões, visto custar tempo, dinheiro e até mesmo experimentar riscos para mudar esse padrão.

Em resumo, não aplique esse design pattern em todo projeto que começar de maneira deliberada. Avalie bem a necessidade, motivação e conhecimentos técnicos seus e do time antes de decidir qual arquitetura usar no projeto.

Pode ser que você encontre artigos em que ao invés dos nomes container e presentational, sejam smart e dumbs, mas como até diz no arquivo de referência do Dan, esses nomes não explicam nada.

Referências

  1. Dan Abramov
  2. @lydiahallie & @addyosmani

Top comments (0)