Skip to content

Instantly share code, notes, and snippets.

@ajitid
Created August 10, 2021 10:57
Show Gist options
  • Save ajitid/2c7d239f00ddfb8e7d36eed037de88ef to your computer and use it in GitHub Desktop.
Save ajitid/2c7d239f00ddfb8e7d36eed037de88ef to your computer and use it in GitHub Desktop.
Tabs component by cloning children
import React, { useState, useMemo } from 'react'
import css from './tabs.module.scss'
/*
usage
<Tabs>
<Tab label="one">
<OneContent />
</Tab>
<Tab label="two" initiallyActive>
<TwoContent />
</Tab>
<Tab label="two">
<TwoContent />
</Tab>
</Tabs>
*/
interface TabProps {
label: string
initiallyActive?: boolean
}
export const Tab: React.FC<TabProps> = ({ children }) => {
return <div className={css.tabcontent}>{children}</div>
}
const Tabs: React.FC = ({ children }) => {
const tabs = useMemo(() => {
// or just -> React.Children.toArray(children).filter(React.isValidElement)
// have to code like this because of TS
const childrenArray = React.Children.toArray(children)
const tabComponents = []
for (const child of childrenArray) {
if (!React.isValidElement(child)) continue
tabComponents.push(child)
}
return tabComponents
}, [children])
const tabsLabel = useMemo(() => tabs.map(tab => tab.props.label), [tabs])
const [activeIndex, setActiveIndex] = useState(() => {
for (const [i, tab] of tabs.entries()) {
if (tab.props.initiallyActive) return i
}
return 0
})
return (
<div className={css.tabs}>
<div className={css.tabbtns}>
{tabsLabel.map((label, i) => (
<button
className={`${css.tab} noselect ${activeIndex === i ? css.tabisselected : ''}`}
key={i}
onClick={() => setActiveIndex(i)}
>
{label}
</button>
))}
</div>
{tabs[activeIndex]}
</div>
)
}
export default Tabs
@ajitid
Copy link
Author

ajitid commented Aug 10, 2021

There is a cloneElement API though I didn't used it. Also, another way would be to implement it using Context API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment