Back to Rules

A pattern for creating flexible and composable components in React

Code

import { createContext, useContext, ReactNode } from 'react'

interface TabsContext {
  activeTab: string
  setActiveTab: (id: string) => void
}

const TabsContext = createContext<TabsContext | null>(null)

function Tabs({ children }: { children: ReactNode }) {
  const [activeTab, setActiveTab] = useState('')
  
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      {children}
    </TabsContext.Provider>
  )
}

function TabList({ children }: { children: ReactNode }) {
  return <div role="tablist">{children}</div>
}

function Tab({ id, children }: { id: string; children: ReactNode }) {
  const context = useContext(TabsContext)
  if (!context) throw new Error('Tab must be used within Tabs')
  
  const { activeTab, setActiveTab } = context
  
  return (
    <button
      role="tab"
      aria-selected={activeTab === id}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  )
}

function TabPanel({ id, children }: { id: string; children: ReactNode }) {
  const context = useContext(TabsContext)
  if (!context) throw new Error('TabPanel must be used within Tabs')
  
  return context.activeTab === id ? <div>{children}</div> : null
}

// Usage
<Tabs>
  <TabList>
    <Tab id="tab1">Tab 1</Tab>
    <Tab id="tab2">Tab 2</Tab>
  </TabList>
  <TabPanel id="tab1">Content 1</TabPanel>
  <TabPanel id="tab2">Content 2</TabPanel>
</Tabs>
react
Posted by Tejashwa Tiwari1/29/2024