616 words4 min read

Criando um simples Tabs component em React

Já faz algum tempo que não falo nada sobre React, e por conta que venho trabalhando tanto com React quanto Angular nos ultimos meses, acho que seria interessante ver como podemos criar composição de componentes utilizando uma estrutura simples a qual ira nos permitir reutilizar-los de maneira simples.

A minha motivação para escrever este artigo foi que em 2 meses, minha equipe e eu reescrevemos nossos componentes que utilizamos em várias aplicações, afim de diminuir a duplicação de código e também para revisarmos as dependências de terceiros de vários projetos que utilizamos atualmente.

Inicialmente utilizávamos muitas dependências do Bootstrap o qual provavelmente foi muito útil no início do projeto. Com o tempo, além de nos custar um pouco de trabalho sobrescrever os estilos dos componentes do Bootstrap, também criávamos outros componentes a partir dos elementos que já existiam.

Além disso, tínhamos (ainda temos) muitos componentes open-source para coisas que não queríamos criar do zero, mas hoje com uma maior maturidade decidimos reescrever algumas partes do software para termos mais controle sobre aquilo que desenvolvemos.

Mantendo a simplicidade

Com objetivo de mostrar como algo simples pode ser feito, vou criar um componente para <Tabs/> que seja flexível para renderizar qualquer jsx como conteúdo da `, e também algo simples que nos proporcionará facilidade para modificação no futuro.

Vamos então desenvolver algo assim:

<Tabs>
  <Tab label={"Tab #1"} className={"tab-custom-class"}>
    <p>content 0</p>
  </Tab>
  <Tab label={"Tab #2"} className={"tab-custom-class"}>
    <CustomComponent propA={"foo"} propB={"bar"} propC={this.handleEvent} />
  </Tab>
</Tabs>

Como podemos ver, <Tab/> deve renderizar qualquer jsx válido. Isso nos traz a flexibilidade de utilizar propriedades customizadas para outras funcionalidades que sejam úteis no futuro.

O componente

O componente <Tabs/> gerencia seu próprio state e sabe qual conteúdo renderizar baseado na <Tab/> selecionada, sendo assim, ele é quem irá mostrar ou esconder conteúdo. Isso também nos traz a capacidade de renderizar uma tab por padrão quando nenhuma for selecionada através do state interno.

Obs: Você pode encontrar o link com o código e o demo no final deste artigo.

class Tabs extends Component {
  constructor(props) {
    super(props);
    this.state = { activeIndex: props.defaultActiveIndex || 0 };
  }

  handleTabClick = (tabIndex) => {
    if (tabIndex !== this.state.activeIndex) {
      this.setState({ activeIndex: tabIndex });
    }
  };

  cloneTabElement = (tab, index = 0) => {
    const { activeIndex } = this.state;

    return cloneElement(tab, {
      onClick: () => this.handleTabClick(index),
      tabIndex: index,
      isActive: index === activeIndex,
    });
  };

  renderChildrenTabs = () => {
    const { children } = this.props;

    if (!Array.isArray(children)) {
      return this.cloneTabElement(children);
    }

    return children.map(this.cloneTabElement);
  };

  renderChildrenTabs = () => {
    const { children } = this.props;
    const { activeIndex } = this.state;

    if (children[activeIndex]) {
      return children[activeIndex].props.children;
    }

    return children.props.children;
  };

  render() {
    const { className } = this.props;

    return (
      <section className={`tabs ${className}`}>
        <ul className={"tabs__list"}>{this.renderChildrenTabs()}</ul>
        <div className={"tabs__content"}>{this.renderActiveTabContent()}</div>
      </section>
    );
  }
}

O componente

O componente <Tab/> é stateless (o que nos permite utilizar um componente funcional), ou seja, o que além de não possuir state, possui o único objetivo de renderizar a tab, permitindo que o desenvolvedor especifique o estilo, e executar uma ação onClick relacionada ao escopo pai (o componente <Tabs/>) quando o evento ocorre.

const Tab = (props) => {
  const { className, label, isActive, onClick } = props;

  const tabClass = `tabs__tab ${className}`;
  const linkClass = isActive ? "tabs__tab-link--active" : "tabs__tab-link";

  return (
    <li className={tabClass}>
      <a className={linkClass} onClick={onClick}>
        {label}
      </a>
    </li>
  );
};

Conclusão

Com apenas um pouco de código e ainda sim flexibilidade, criamos um componente <Tabs/> simples, o qual nos permite evoluir-lo se necessário. O código fonte necessário para implementar os componentes podem ser encontrados neste gist, e o pequeno exemplo aqui.

Já implementou algo similar? Você faria algo diferente? Deixe um comentário e vamos aprender juntos :)