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
Postar um comentário