Desenvolvimento

8 jun, 2018

Pensando em componentes de interface com exemplos em React

Publicidade

Quando a web se tornou dominada por frameworks CSS, JavaScript e SPAs maravilhosas, a comunidade começou a falar mais sobre componentização. Porém, nem todo mundo consegue entender muito bem o conceito de componente por falta de costume de particionar softwares.

Criar componentes ou módulos em um software é extremamente útil devido a possibilidade de reutilização de uma funcionalidade em diversas partes do nosso sistema. Quando pensamos em componentes de uma interface, podemos imaginar os pedaços da tela que podem se repetir na mesma tela ou em outras telas da aplicação.

Seguindo o exemplo da Pokédex, que comentei no último artigo, vamos pegar o desenho da aplicação e imaginar componentes para ela.

Para revisar o funcionamento desse app, na imagem temos o desenho de uma Pokédex fechada, e quando o usuário clicar no centro dela, ela mudará de estado para aberta, exibindo uma caixa de busca.

Minha Pokédex foi baseada na imagem que encontrei no project-pokemon.

Se você quiser olhar o código inicial, seria este aqui, onde temos o seguinte layout renderizado:

Onde temos a Pokédex com a capa fechada e quando clicamos no botão ao centro, o visor se expande.

O código dessa aplicação está todo em um arquivo somente, pois o primeiro artigo foi focado somente em pensarmos no estado da aplicação. Agora vamos trabalhar em transformar isso em componentes.

<span style="color: #008800; font-weight: bold;">import</span> React, { Component } from <span style="background-color: #fff0f0;">'react'</span>;
<span style="color: #008800; font-weight: bold;">import</span> <span style="background-color: #fff0f0;">'./App.css'</span>;

<span style="color: #008800; font-weight: bold;">class</span> App <span style="color: #008800; font-weight: bold;">extends</span> Component {
  constructor(props) {
    <span style="color: #008800; font-weight: bold;">super</span>(props);
    <span style="color: #008800; font-weight: bold;">this</span>.state <span style="color: #333333;">=</span> {
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">false</span>
    }
  }

  toggleVisor <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
    <span style="color: #008800; font-weight: bold;">this</span>.setState({
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #333333;">!</span><span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive
    })
  }

  renderActionButton <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
    <span style="color: #008800; font-weight: bold;">if</span> (<span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive) {
      <span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>input type<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"text"</span> <span style="color: #333333;">/&gt;</span>
      )
    }
    <span style="color: #008800; font-weight: bold;">return</span> (
      <span style="color: #333333;">&lt;</span>button
        className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"visor__action-button"</span>
        onClick<span style="color: #333333;">=</span>{<span style="color: #008800; font-weight: bold;">this</span>.toggleVisor}<span style="color: #333333;">&gt;</span>
      <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/button&gt;</span>
    )
  }
  render() {
    <span style="color: #008800; font-weight: bold;">return</span> (
      <span style="color: #333333;">&lt;</span>div className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"pokedex"</span><span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span>div className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"top-bar"</span><span style="color: #333333;">&gt;&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
        <span style="color: #333333;">&lt;</span>div
          className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"visor"</span>
          style<span style="color: #333333;">=</span>{{ height<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive <span style="color: #333333;">?</span> <span style="background-color: #fff0f0;">'420px'</span> <span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">''</span> }}<span style="color: #333333;">&gt;</span>
          { <span style="color: #008800; font-weight: bold;">this</span>.renderActionButton() }
        <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
        <span style="color: #333333;">&lt;</span>div className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bottom-bar"</span><span style="color: #333333;">&gt;&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
      <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    );
  }
}

<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> App;

De maneira bem simplista, vamos criar os seguintes componentes:

  • Tampa da Pokédex
  • Visor
  • Botão
  • Componente de pesquisa

Conforme formos evoluindo uma grande aplicação, podem surgir mais necessidades de criação de componentes, como a criação de algo somente para ser um botão que é replicado em todos os lugares, um <li> com estilo especial que deve ser utilizado em todas as listas e assim por diante. Mas por enquanto não seremos tão detalhistas, pois não temos essa necessidade.

Para facilitar nosso pensamento em componentização, podemos olhar essa thread muito boa no Twitter, iniciada pelo Fernando Daciuk.

A estrutura final dos nossos componentes ficará:

src

├── components
│ ├── ActionButton
│ ├── Cover
│ ├── SearchInput
│ └── Visor

Vamos fazer isso!

Criando nossos componentes React

Começando pela tampa da nossa Pokédex, criamos uma pasta com o nome do elemento que iremos criar, pois ao criamos um componente, chamamos ele no código React assim como chamamos um elemento HTML.

Criamos uma pasta chamada Cover, dentro de uma pasta chamada components, e dentro dela adicionamos os arquivos index.js e index.css. O arquivo .js será onde vamos adicionar o código que renderiza nosso componente e o .css é onde ficarão os estilos desse respectivo componente.

Teremos a seguinte estrutura:

src

├── App.css
├── App.js
├── App.test.js
├── components
│ ├── Cover
│ │ ├── index.css
│ │ └── index.js
├── index.js

Já podemos importar o componente Cover em nosso App.js, o arquivo principal da nossa aplicação.

<span style="color: #008800; font-weight: bold;">import</span> React, { Component } from <span style="background-color: #fff0f0;">'react'</span>;
<span style="color: #008800; font-weight: bold;">import</span> <span style="background-color: #fff0f0;">'./App.css'</span>;
<span style="color: #008800; font-weight: bold;">import</span> Cover from <span style="background-color: #fff0f0;">'./components/Cover'</span>

No nosso método render() do App.js, já vamos mudar para a utilização do componente Cover com uma propriedade chamada position, que irá dizer ao nosso componente em qual posição ele está para que receba a classe que determina seus estilos.

 render() {
    <span style="color: #008800; font-weight: bold;">return</span> (
      <span style="color: #333333;">&lt;</span>div className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"pokedex"</span><span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"top"</span> <span style="color: #333333;">/&gt;</span>
        <span style="color: #333333;">&lt;</span>div
          className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"visor"</span>
          style<span style="color: #333333;">=</span>{{ height<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive <span style="color: #333333;">?</span> <span style="background-color: #fff0f0;">'420px'</span> <span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">''</span> }}<span style="color: #333333;">&gt;</span>
          { <span style="color: #008800; font-weight: bold;">this</span>.renderActionButton() }
        <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bottom"</span> <span style="color: #333333;">/&gt;</span>
      <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    );
  }

No nosso arquivo index.css dentro da pasta Cover, teremos somente as classes que usamos para renderizar o componente como parte de cima ou de baixo da Pokédex.

<span style="color: #bb0066; font-weight: bold;">.top-bar</span> {
  <span style="color: #008800; font-weight: bold;">border</span><span style="color: #333333;">-</span>radius<span style="color: #333333;">:</span> <span style="color: #6600ee; font-weight: bold;">10px</span> <span style="color: #6600ee; font-weight: bold;">10px</span> <span style="color: #6600ee; font-weight: bold;">0</span> <span style="color: #6600ee; font-weight: bold;">0</span>;
  <span style="color: #008800; font-weight: bold;">border-bottom</span><span style="color: #333333;">:</span> <span style="color: #6600ee; font-weight: bold;">5px</span> <span style="color: #008800; font-weight: bold;">solid</span> <span style="color: #007020;">black</span>;
}

<span style="color: #bb0066; font-weight: bold;">.bottom-bar</span> {
  <span style="color: #008800; font-weight: bold;">border</span><span style="color: #333333;">-</span>radius<span style="color: #333333;">:</span> <span style="color: #6600ee; font-weight: bold;">0</span> <span style="color: #6600ee; font-weight: bold;">0</span> <span style="color: #6600ee; font-weight: bold;">10px</span> <span style="color: #6600ee; font-weight: bold;">10px</span>;
  <span style="color: #008800; font-weight: bold;">border-top</span><span style="color: #333333;">:</span> <span style="color: #6600ee; font-weight: bold;">5px</span> <span style="color: #008800; font-weight: bold;">solid</span> <span style="color: #007020;">black</span>;
}

Agora podemos criar o nosso componente no index.js de dentro da pasta Cover com o seguinte conteúdo:

<span style="color: #008800; font-weight: bold;">import</span> React from <span style="background-color: #fff0f0;">'react'</span>
<span style="color: #008800; font-weight: bold;">import</span> <span style="background-color: #fff0f0;">'./index.css'
</span>
<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> <span style="color: #008800; font-weight: bold;">function</span> Cover(props) {
    <span style="color: #008800; font-weight: bold;">const</span> positionsMap <span style="color: #333333;">=</span> {
        <span style="background-color: #fff0f0;">'top'</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'top-bar'</span>,
        <span style="background-color: #fff0f0;">'bottom'</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'bottom-bar'</span>
    }
    <span style="color: #008800; font-weight: bold;">const</span> position <span style="color: #333333;">=</span> props.position
    <span style="color: #008800; font-weight: bold;">const</span> positionClass <span style="color: #333333;">=</span> positionsMap[position]
    <span style="color: #008800; font-weight: bold;">const</span> styles <span style="color: #333333;">=</span> {

        flex<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'1'</span>,
        backgroundColor<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'#c40c24'</span>,
        border<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'1px solid #52181c'</span>,
        height<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'210px'</span>
    }

    <span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>div
            style<span style="color: #333333;">=</span>{ styles }
            className<span style="color: #333333;">=</span>{ positionClass }<span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    )
}

Onde estamos importando os estilos do componente:

<span style="color: #008800; font-weight: bold;">import</span> <span style="background-color: #fff0f0;">'./index.css'</span>

Em seguida, criamos um componente com function, e não com class, como no App.js. Faremos isso porque este componente não terá estado, será stateless e receberá todos os dados e parâmetros através do componente que está chamando ele com o uso de props.

<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> <span style="color: #008800; font-weight: bold;">function</span> Cover(props)

Para entender melhor sobre componentes stateless e statefull, dê uma conferida no artigo do Guilherme Oderdenge:

Foi criado um mapa com as classes para top e bottom da tampa da nossa Pokédex:

  <span style="color: #008800; font-weight: bold;">const</span> positionsMap <span style="color: #333333;">=</span> {
        <span style="background-color: #fff0f0;">'top'</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'top-bar'</span>,
        <span style="background-color: #fff0f0;">'bottom'</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'bottom-bar'</span>
    }

E para dizer para o nosso componente qual a classe que ele deve receber, de acordo com a posição que ele foi colocado, acessamos a chave do mapa através de props.position.

  <span style="color: #008800; font-weight: bold;">const</span> position <span style="color: #333333;">=</span> props.position

    <span style="color: #008800; font-weight: bold;">const</span> positionClass <span style="color: #333333;">=</span> positionsMap[position]

E depois passamos o positionClass para o className da <div> que será retornada.

<span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>div
            style<span style="color: #333333;">=</span>{ styles }
            className<span style="color: #333333;">=</span>{ positionClass }<span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    )

É interessante reparar, aqui, que o estilo compartilhado entre as duas partes da tampa (top e bottom) ficou inline através do mapa criado aqui:

  <span style="color: #007700;">const</span> <span style="color: #007700;">styles</span> <span style="color: #333333;">=</span> {
        flex<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'1'</span><span style="color: #333333;">,</span>
        backgroundColor<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'#c40c24'</span><span style="color: #333333;">,</span>
        <span style="color: #008800; font-weight: bold;">border</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'1px solid #52181c'</span><span style="color: #333333;">,</span>
        <span style="color: #008800; font-weight: bold;">height</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'210px'</span>
    }

E utilizado com a tag style:

 <span style="color: #007700;">return</span> <span style="color: #333333;">(</span>
        <span style="color: #333333;">&lt;</span><span style="color: #007700;">div</span>
            <span style="color: #007700;">style</span><span style="color: #333333;">=</span>{ styles }
            <span style="color: #007700;">className</span><span style="color: #333333;">=</span>{ positionClass }<span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;/</span><span style="color: #007700;">div</span><span style="color: #333333;">&gt;</span>
    <span style="color: #333333;">)</span>

Repare que uma propriedade com um traço se torna camelCase para que o React consiga entender ela:

background-color === backgroundColor.

Mas e se nenhuma propriedade for passada na hora em que invocarmos o componente Cover?

A aplicação será renderizada e não receberemos nenhum erro.

Para garantir que a próxima pessoa desenvolvedora que for utilizar nosso componente coloque a propriedade position, podemos adicionar o prop-types, que faz a validação dos tipos que recebemos em props, garantindo que aquilo que esperamos está sendo recebido.

O prop-types, hoje em dia, é um pacote separado do React e por isso é necessário a sua instalação através do NPM ou Yarn. E para utilizar o pacote, será necessário acrescentar as seguintes linhas ao nosso componente Cover:

<span style="color: #007700;">import</span> <span style="color: #007700;">React</span> <span style="color: #007700;">from</span> <span style="background-color: #fff0f0;">'react'</span>
<span style="color: #007700;">import</span> <span style="color: #007700;">PropTypes</span> <span style="color: #007700;">from</span> <span style="background-color: #fff0f0;">'prop-types'</span>
<span style="color: #007700;">import</span> <span style="background-color: #fff0f0;">'./index.css'</span>

<span style="color: #007700;">export</span> <span style="color: #007700;">default</span> <span style="color: #007700;">function</span> <span style="color: #007700;">Cover</span><span style="color: #333333;">(</span><span style="color: #007700;">props</span><span style="color: #333333;">)</span> {
    const positionsMap <span style="color: #333333;">=</span> <span style="color: #ff0000; background-color: #ffaaaa;">{</span>
        <span style="background-color: #fff0f0;">'top'</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'top-bar'</span><span style="color: #333333;">,</span>
        <span style="background-color: #fff0f0;">'bottom'</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'bottom-bar'</span>
    }
    <span style="color: #007700;">const</span> <span style="color: #007700;">position</span> <span style="color: #333333;">=</span> <span style="color: #007700;">props</span><span style="color: #bb0066; font-weight: bold;">.position</span>
    <span style="color: #007700;">const</span> <span style="color: #007700;">positionClass</span> <span style="color: #333333;">=</span> <span style="color: #007700;">positionsMap</span><span style="color: #333333;">[</span><span style="color: #007700;">position</span><span style="color: #333333;">]</span>
    <span style="color: #007700;">const</span> <span style="color: #007700;">styles</span> <span style="color: #333333;">=</span> {
        flex<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'1'</span><span style="color: #333333;">,</span>
        backgroundColor<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'#c40c24'</span><span style="color: #333333;">,</span>
        <span style="color: #008800; font-weight: bold;">border</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'1px solid #52181c'</span><span style="color: #333333;">,</span>
        <span style="color: #008800; font-weight: bold;">height</span><span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'210px'</span>
    }

    <span style="color: #007700;">return</span> <span style="color: #333333;">(</span>
        <span style="color: #333333;">&lt;</span><span style="color: #007700;">div</span>
            <span style="color: #007700;">style</span><span style="color: #333333;">=</span>{ styles }
            <span style="color: #007700;">className</span><span style="color: #333333;">=</span>{ positionClass }<span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;/</span><span style="color: #007700;">div</span><span style="color: #333333;">&gt;</span>

    <span style="color: #333333;">)</span>
<span style="color: #ff0000; background-color: #ffaaaa;">}</span>

E quando a aplicação for renderizada, a pessoa receberá a seguinte mensagem:

“Warning: Failed prop type: The prop `position` is marked as required in `Cover`, but its value is `undefined`.”

Pronto! Nosso primeiro componente está criado.

Agora vamos partir para o componente Visor. Seguindo a mesma regra, criamos uma pasta Visor dentro de components e os arquivos index.js e index.css.

Precisamos colocar o import do componente Cover no nosso App.js.

<span style="color: #008800; font-weight: bold;">import</span> Visor from <span style="background-color: #fff0f0;">'./components/Visor'</span>

E invocar nosso componente passando a propriedade isVisorActive, para que saibamos se devemos aplicar o tamanho de 420px ou não no visor.

    <span style="color: #007700;">&lt;Visor</span>

          <span style="color: #0000cc;">isVisorActive=</span><span style="background-color: #fff0f0;">{</span> <span style="color: #ff0000; background-color: #ffaaaa;">this.state.isVisorActive</span> <span style="color: #ff0000; background-color: #ffaaaa;">}</span> <span style="color: #007700;">/&gt;</span>

O código de App.js ficará assim:

<span style="color: #008800; font-weight: bold;">import</span> React, { Component } from <span style="background-color: #fff0f0;">'react'</span>;
<span style="color: #008800; font-weight: bold;">import</span> <span style="background-color: #fff0f0;">'./App.css'</span>;
<span style="color: #008800; font-weight: bold;">import</span> Cover from <span style="background-color: #fff0f0;">'./components/Cover'</span>
<span style="color: #008800; font-weight: bold;">import</span> Visor from <span style="background-color: #fff0f0;">'./components/Visor'</span>

<span style="color: #008800; font-weight: bold;">class</span> App <span style="color: #008800; font-weight: bold;">extends</span> Component {
  constructor(props) {
    <span style="color: #008800; font-weight: bold;">super</span>(props);
    <span style="color: #008800; font-weight: bold;">this</span>.state <span style="color: #333333;">=</span> {
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">false</span>
    }
  }

  toggleVisor <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
    <span style="color: #008800; font-weight: bold;">this</span>.setState({
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #333333;">!</span><span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive
    })
  }

  renderActionButton <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
    <span style="color: #008800; font-weight: bold;">if</span> (<span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive) {
      <span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>input type<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"text"</span> <span style="color: #333333;">/&gt;</span>
      )
    }
    <span style="color: #008800; font-weight: bold;">return</span> (
      <span style="color: #333333;">&lt;</span>button
        className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"visor__action-button"</span>
        onClick<span style="color: #333333;">=</span>{<span style="color: #008800; font-weight: bold;">this</span>.toggleVisor}<span style="color: #333333;">&gt;</span>
      <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/button&gt;</span>
    )
  }

  render() {
    <span style="color: #008800; font-weight: bold;">return</span> (
      <span style="color: #333333;">&lt;</span>div className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"pokedex"</span><span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"top"</span> <span style="color: #333333;">/&gt;</span>
        <span style="color: #333333;">&lt;</span>Visor
          isVisorActive<span style="color: #333333;">=</span>{ <span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive } <span style="color: #333333;">/&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bottom"</span> <span style="color: #333333;">/&gt;</span>
      <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    );
  }
}

<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> App;

Como o componente Visor agrega o componente ActionButton, que tal já movermos a chamada do componente para dentro do Visor?

O código do App.js, agora mais limpo, ficará assim:

<span style="color: #008800; font-weight: bold;">import</span> React, { Component } from <span style="background-color: #fff0f0;">'react'</span>;
<span style="color: #008800; font-weight: bold;">import</span> <span style="background-color: #fff0f0;">'./App.css'</span>;
<span style="color: #008800; font-weight: bold;">import</span> Cover from <span style="background-color: #fff0f0;">'./components/Cover'</span>
<span style="color: #008800; font-weight: bold;">import</span> Visor from <span style="background-color: #fff0f0;">'./components/Visor'</span>

<span style="color: #008800; font-weight: bold;">class</span> App <span style="color: #008800; font-weight: bold;">extends</span> Component {
  constructor(props) {
    <span style="color: #008800; font-weight: bold;">super</span>(props);
    <span style="color: #008800; font-weight: bold;">this</span>.state <span style="color: #333333;">=</span> {
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">false</span>
    }
  }

  toggleVisor <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
    <span style="color: #008800; font-weight: bold;">this</span>.setState({
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #333333;">!</span><span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive
    })
  }

  render() {
    <span style="color: #008800; font-weight: bold;">return</span> (
      <span style="color: #333333;">&lt;</span>div className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"pokedex"</span><span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"top"</span> <span style="color: #333333;">/&gt;</span>
        <span style="color: #333333;">&lt;</span>Visor
          isVisorActive<span style="color: #333333;">=</span>{ <span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive }
          toggleVisor<span style="color: #333333;">=</span>{ <span style="color: #008800; font-weight: bold;">this</span>.toggleVisor } <span style="color: #333333;">/&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bottom"</span> <span style="color: #333333;">/&gt;</span>
      <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    );
  }
}

<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> App;

Repare que passamos o método que muda o estado do visor em uma prop para ser utilizada pelo nosso botão e no código do index.js de Visor teremos o seguinte:

<span style="color: #008800; font-weight: bold;">import</span> React from <span style="background-color: #fff0f0;">'react'</span>
<span style="color: #008800; font-weight: bold;">import</span> <span style="background-color: #fff0f0;">'./index.css'</span>

<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> <span style="color: #008800; font-weight: bold;">function</span> Visor(props) {
    <span style="color: #008800; font-weight: bold;">const</span> isVisorActive <span style="color: #333333;">=</span> props.isVisorActive

    <span style="color: #008800; font-weight: bold;">const</span> renderActionButton <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
        <span style="color: #008800; font-weight: bold;">if</span> (isVisorActive) {
            <span style="color: #008800; font-weight: bold;">return</span> (
                <span style="color: #333333;">&lt;</span>input type<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"text"</span> <span style="color: #333333;">/&gt;</span>
            )
        }
        <span style="color: #008800; font-weight: bold;">return</span> (
            <span style="color: #333333;">&lt;</span>button
                className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"visor__action-button"</span>
                onClick<span style="color: #333333;">=</span>{ props.toggleVisor }<span style="color: #333333;">&gt;</span>
            <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/button&gt;</span>
        )
    }

    <span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>div
            className<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"visor"</span>
            style<span style="color: #333333;">=</span>{{ height<span style="color: #333333;">:</span> isVisorActive <span style="color: #333333;">?</span> <span style="background-color: #fff0f0;">'420px'</span> <span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">''</span> }}<span style="color: #333333;">&gt;</span>
            { renderActionButton() }
        <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    )
}

E agora podemos criar nosso componente de botão que abre a Pokédex e o componente de busca, mas antes disso, podemos remover o arquivo index.css do nosso App.js, pois como é somente uma classe, podemos mover os estilos para uma tag style:

<span style="color: #008800; font-weight: bold;">class</span> App <span style="color: #008800; font-weight: bold;">extends</span> Component {
  constructor(props) {
    <span style="color: #008800; font-weight: bold;">super</span>(props);
    <span style="color: #008800; font-weight: bold;">this</span>.state <span style="color: #333333;">=</span> {
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #008800; font-weight: bold;">false</span>
    }
  }

  toggleVisor <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
    <span style="color: #008800; font-weight: bold;">this</span>.setState({
      isVisorActive<span style="color: #333333;">:</span> <span style="color: #333333;">!</span><span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive
    })
  }

  render() {
    <span style="color: #008800; font-weight: bold;">const</span> styles <span style="color: #333333;">=</span> {
      display<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'flex'</span>,
      flexDirection<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'column'</span>,
      maxWidth<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'420px'</span>
    }

    <span style="color: #008800; font-weight: bold;">return</span> (
      <span style="color: #333333;">&lt;</span>div style<span style="color: #333333;">=</span>{ styles }<span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"top"</span> <span style="color: #333333;">/&gt;</span>
        <span style="color: #333333;">&lt;</span>Visor
          isVisorActive<span style="color: #333333;">=</span>{ <span style="color: #008800; font-weight: bold;">this</span>.state.isVisorActive }
          toggleVisor<span style="color: #333333;">=</span>{ <span style="color: #008800; font-weight: bold;">this</span>.toggleVisor } <span style="color: #333333;">/&gt;</span>
        <span style="color: #333333;">&lt;</span>Cover position<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"bottom"</span> <span style="color: #333333;">/&gt;</span>
      <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/div&gt;</span>
    );
  }
}

Vamos voltar para o nosso componente Visor e remover as divs soltas de lá. Criaremos o componente ActionButton e o SearchInput, que serão o botão que ativa a Pokédex e o componente de busca por Pokémons.

Será necessário criar as pastas para os componentes: ActionButton e SearchInput, porém, vamos criar um por vez.

Na pasta ActionButton vamos criar o arquivo index.js com tudo o que precisamos para que o nosso botão funcione. Lembrando que ele também será um stateless component e seus estilos podem ser adicionados através do objeto styles.

<span style="color: #008800; font-weight: bold;">import</span> React from <span style="background-color: #fff0f0;">'react'</span>

<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> <span style="color: #008800; font-weight: bold;">function</span> ActionButton(props) {
    <span style="color: #008800; font-weight: bold;">const</span> styles <span style="color: #333333;">=</span> {
        backgroundColor<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'white'</span>,
        border<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'1px solid #b4e5ec'</span>,
        borderRadius<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'50%'</span>,
        width<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'160px'</span>,
        height<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'160px'</span>,
        position<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'absolute'</span>,
        outline<span style="color: #333333;">:</span> <span style="background-color: #fff0f0;">'none'</span>
    } 

    <span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>button
            style<span style="color: #333333;">=</span>{ styles }
            onClick<span style="color: #333333;">=</span>{ props.action }<span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/button&gt;</span>
    )
}

Repare que aqui chamamos a prop que carrega o método que será executado no onClick de action, isso porque ela é uma ação genérica que será executada e o componente não precisa saber exatamente o que é, mas que é algo que deve fazer.

<span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>button
            style<span style="color: #333333;">=</span>{ styles }
            onClick<span style="color: #333333;">=</span>{ props.action }<span style="color: #333333;">&gt;</span>
        <span style="color: #333333;">&lt;</span><span style="color: #ff0000; background-color: #ffaaaa;">/button&gt;</span>
    )

E no componente Visor, podemos mudar o renderActionButton para:

 <span style="color: #008800; font-weight: bold;">const</span> renderActionButton <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
        <span style="color: #008800; font-weight: bold;">if</span> (isVisorActive) {
            <span style="color: #008800; font-weight: bold;">return</span> (
                <span style="color: #333333;">&lt;</span>input type<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"text"</span> <span style="color: #333333;">/&gt;</span>
            )
        }
        <span style="color: #008800; font-weight: bold;">return</span> (
            <span style="color: #333333;">&lt;</span>ActionButton action<span style="color: #333333;">=</span>{ props.toggleVisor }<span style="color: #333333;">/&gt;</span>
        )
    }

Agora podemos partir para o componente SearchInput, criando o arquivo index.js dentro da pasta SearchInput com o seguinte conteúdo:

<span style="color: #008800; font-weight: bold;">import</span> React from <span style="background-color: #fff0f0;">'react'</span>

<span style="color: #008800; font-weight: bold;">export</span> <span style="color: #008800; font-weight: bold;">default</span> <span style="color: #008800; font-weight: bold;">function</span> SearchInput(props) {
    <span style="color: #008800; font-weight: bold;">return</span> (
        <span style="color: #333333;">&lt;</span>input type<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"text"</span> <span style="color: #333333;">/&gt;</span>
    )
}

E no componente Visor nós importamos o SearchInput:

<span style="color: #008800; font-weight: bold;">import</span> SearchInput from <span style="background-color: #fff0f0;">'../SearchInput'</span>

E o utilizamos em renderActionButton<span style="color: #333333;">:</span>

    <span style="color: #008800; font-weight: bold;">const</span> renderActionButton <span style="color: #333333;">=</span> () <span style="color: #333333;">=&gt;</span> {
        <span style="color: #008800; font-weight: bold;">if</span> (isVisorActive) {
            <span style="color: #008800; font-weight: bold;">return</span>(
                <span style="color: #333333;">&lt;</span>SearchInput <span style="color: #333333;">/&gt;</span>
            )
        }
        <span style="color: #008800; font-weight: bold;">return</span> (
            <span style="color: #333333;">&lt;</span>ActionButton action<span style="color: #333333;">=</span>{ props.toggleVisor }<span style="color: #333333;">/&gt;</span>
        )
    }

E pronto! Nossos componentes foram separados de acordo com sua responsabilidade e temos um código que pode ser reaproveitado.

Se você quiser ver o resultado final da Pokedéx com React, pode conferir neste repositório:

Conclusão

Utilizando uma biblioteca/framework ou não, é muito importante pensar na reutilização de código para que nosso trabalho seja mais produtivo, para que o code base seja menor e a manutenção do software seja menos custosa, afinal, desenvolver software custa dinheiro.

Espero ter conseguido ensinar um pouco mais sobre componentes em React, e nos próximos artigos vou compartilhar um pouco mais sobre o assunto.

O código final para este exemplo está neste link.

Me acompanhe no Twitter para saber quando eu lançar mais artigos sobre React, neste link.