
Today I needed to make a tab system.
Perfect for display multiple types of data in a small space, a tab system has two parts :
- The header always display all the tabs labels
- The content part display the data associated to the selected tab
The complexity of this kind of system is that we have a fixed part and a dynamic part, let's see two implementations.
V1 – Simple to code, hard to use
A first idea is to do a simple component with atabs
prop corresponding to an array of objects with alabel
and acontent
which can be called like this :
<TabViewtabs={[{label:"First tab",content:<p>My first tab content</p>},{label:"Second tab",content:<p>My second tab content</p>},{label:"Third tab",content:<p>My third tab content</p>}]}/>
I could put content into variable, but it's for the example
The corresponding<TabView>
component should look like this :
constTabView=({tabs})=>{const[selectedTabIndex,setSelectedTabIndex]=useState(0)return(<div><divclassName="header">{tabs.map(tab=>(<p>{tab.label}</p>))}</div><divclassName="content">{tabs[selectedTabIndex].content}</div></div>)}
First problem, I need a conditional tab and with this configuration it's complicated 😕
We have to put the tabs into a variable and add an optional tab if necessary... Something like that :
constdisplayThirdTab=...consttabs=[{label:"First tab",content:<p>My first tab content</p>},{label:"Second tab",content:<p>My second tab content</p>}]if(displayThirdTab){tabs.push({label:"Third tab",content:<p>My third tab content</p>})}return(<TabViewtabs={tabs}/>)
It's starting to get complicated to use, and we can do better. If we change my<TabView>
component, we can make a moredev-friendly component which is used like that :
<TabView><Tablabel="First tab"><p>My first tab content</p></Tab><Tablabel="Second tab"><p>My second tab content</p></Tab>{displayThirdTab&&(<Tablabel="Third tab"><p>My third tab content</p></Tab>)}</TabView>
V2 – Not so difficult to code, much easier to use
The difficulty with the above component lies in the fixed part. We need to display only a part of the children.
To do this, we start by creating a "ghost-component" called<Tab>
which will render nothing
constTab=({tabs})=>{//Rendered in TabView componentreturnnull}
With typescript, we can specify the props we need to use them in<TabView>
Then, we will write the base of the<TabView>
component.
constTabView=({children})=>{const[selectedTabIndex,setSelectedTabIndex]=useState(0)consttabsInfo=[]consttabsContent=[]//TODO : Parse childrenreturn(<div><divclassName="header">{tabsInfo.map(({label})=>(<p>{label}</p>))}</div><divclassName="content">{tabsContent[selectedTabIndex]}</div></div>)}
You can see two arrays :
tabsInfo
will contain all the tabs headers data (just a label in our case)tabsContent
will contain all the<Tab>
componentschildren
props
We now need to parse thechildren
prop to fill our arrays.
To do this, we add a function calledparseTab
constparseTab=(node)=>{//We extract children from the <Tab> propstabsContents.push(node.props.children)//We extract label from <Tab> propstabsInfo.push({label:node.props.label})}
We just have to call it for each node in children with theReact.Children.map
React.Children.map(children,parseTab)
Here we are, our final<TabView>
component
constTabView=({children})=>{const[selectedTabIndex,setSelectedTabIndex]=useState(0)consttabsInfo=[]consttabsContent=[]constparseTab=(node)=>{//We extract children from the <Tab> propstabsContents.push(node.props.children)//We extract label from <Tab> propstabsInfo.push({label:node.props.label})}React.Children.map(children,parseTab)return(<div><divclassName="header">{tabsInfo.map(({label})=>(<p>{label}</p>))}</div><divclassName="content">{tabsContent[selectedTabIndex]}</div></div>)}
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse