Como Criar um Componente de Tabs com React e TypeScript Usando Contexto Controlado


Neste tutorial, você aprenderá a criar um componente de abas (tabs) reutilizável com React e TypeScript. A lógica será centralizada no componente
Tabs, que controla o valor da aba ativa (value) e fornece um callback onChange via contexto para os demais componentes (TabButton e TabContent).

Essa abordagem oferece mais flexibilidade e controle externo, ideal para criar componentes reutilizáveis em bibliotecas ou design systems.

Criando o Contexto: TabContext

O TabContext será responsável por fornecer o value e o onChange para os componentes filhos, sem que eles precisem gerenciar o estado internamente.

// TabContext.tsx
import { createContext, useContext } from 'react';

type TabContextType = {
  value: string;
  onChange: (value: string) => void;
};

export const TabContext = createContext<TabContextType | undefined>(undefined);

export const useTabContext = () => {
  const context = useContext(TabContext);
  if (!context) throw new Error('useTabContext must be used within a Tabs component');
  return context;
};

Componente Tabs

Este é o componente controlado que fornece value e onChange para o contexto. Ele encapsula os outros componentes e centraliza a lógica de controle.

// Tabs.tsx
import { ReactNode } from 'react';
import { TabContext } from './TabContext';

type TabsProps = {
  value: string;
  onChange: (value: string) => void;
  children: ReactNode;
};

export const Tabs = ({ value, onChange, children }: TabsProps) => {                  
  return (
    <TabContext.Provider value={{ value, onChange }}>
      {children}
    </TabContext.Provider>
  );
};

Componente TabButton

Cada botão de aba consome o valor atual e o método de troca de aba via contexto. Quando clicado, chama onChange com o valor da aba correspondente.

// TabButton.tsx
import { useTabContext } from './TabContext';

type TabButtonProps = {
  label: string;
  tabId: string;
};

export const TabButton = ({ label, tabId }: TabButtonProps) => {                          
  const { value, onChange } = useTabContext();

  const isActive = value === tabId;

  return (
    <button
      onClick={() => onChange(tabId)}
      style={{
        fontWeight: isActive ? 'bold' : 'normal',
        borderBottom: isActive ? '2px solid black' : 'none',
      }}
    >
      {label}
    </button>
  );
};

Componente TabContent

Exibe o conteúdo apenas se a aba estiver ativa. O valor da aba ativa é recebido via contexto.

// TabContent.tsx
import { ReactNode } from 'react';
import { useTabContext } from './TabContext';

type TabContentProps = {
  children: ReactNode;
  tabId: string;
};

export const TabContent = ({ children, tabId }: TabContentProps) => {                
  const { value } = useTabContext();

  return value === tabId ? <div>{children}</div> : null;
};

Exemplo de Uso do Componente

Um exemplo prático usando o componente Tabs de forma controlada. O estado é gerenciado no componente pai.

// TabExample.tsx
import { useState } from 'react';
import { Tabs } from './Tabs';
import { TabButton } from './TabButton';
import { TabContent } from './TabContent';

export const TabExample = () => {
  const [tab, setTab] = useState('tab1');

  return (
    <Tabs value={tab} onChange={setTab}>
      <div style={{ display: 'flex', gap: '1rem' }}>
        <TabButton label="Aba 1" tabId="tab1" />                                                    
        <TabButton label="Aba 2" tabId="tab2" />
      </div>

      <TabContent tabId="tab1">
        <p>Conteúdo da Aba 1</p>
      </TabContent>
      <TabContent tabId="tab2">
        <p>Conteúdo da Aba 2</p>
      </TabContent>
    </Tabs>
  );
};

Conclusão

Você acabou de criar um sistema de abas controlado, desacoplado e reutilizável com React e TypeScript. O uso de value e onChange diretamente no componente Tabs permite total controle externo, mantendo os outros componentes simples e focados.

Essa estrutura é perfeita para bibliotecas de UI, pois segue padrões amplamente usados em componentes como Select, RadioGroup e Tabs de bibliotecas como Material UI e Radix UI.



Comentários