'Simple tabs component in ReactJS
I am new to React.js, I know basic stuff like state, components, but I need to:
Create a MyTabsComponent to be used like this:
<MyTabsComponent>
<div title={"Section title 1"}>Content of section 1</div>
<div title={"Section title 2"}>Content of section 2</div>
<div title={"Section title 3"}>Content of section 2</div>
<div title={"Section title 4"}>Content of section 2</div>
<div title={"Section title 5"}>Content of section 2</div>
.
.
.
.
.and so on..............
.
.
.
.
.
</MyTabsComponent>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The above code should render like this:
<div class="tabs">
<button class="btn-active">Section title 1</button>
<button class="btn">Section title 2</button>
<button class="btn">Section title 3</button>
<button class="btn">Section title 4</button>
<!--
.
.
.
.
.
.
and so on..........
-->
<div class="view">
Content of section 1
</div>
</div>
What I've tried:
import React,{useState} from "react";
const MyTabsComponent = () => {
const [title,setTitle] = useState(['Section 1','Section 2','Section 3']);
const [ct,setCt] = useState(null);
return (
<div className="tabs">
<TabButtons tabName={title} tabCt={setCt} />
<div class="view">
Content : {ct}
</div>
</div>
);
};
const TabButtons = (props) => {
return (
<>
{
props.tabName.map((item,i)=>
<button onClick={()=>props.tabCt(i)} className="btn">{item}</button>
)
}
</>
);
};
export default MyTabsComponent;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I don't know how to do this for any number of tabs. Rest I can handle-- CSS, onClick events I understand well, as I know JS well.
EDIT: I found an article on Compound Components https://blog.logrocket.com/understanding-react-compound-components/
and they say:
import React, { useState, createContext, useContext } from "react";
//the name of this context will be DataContext
const DataContext = createContext({});
function Tab({ id, children }) {
//extract the 'setActiveTabID` method from the DataContext state.
const [, setActiveTabID] = useContext(DataContext);
return (
<div>
<div onClick={() => setActiveTabID(id)}>{children}</div>
</div>
);
}
function TabPanel({ whenActive, children }) {
//get the 'activeTabID' state from DataContext.
const [activeTabID] = useContext(DataContext);
return <div>{activeTabID === whenActive ? children : null}</div>;
}
function TabSwitcher(props) {
const [activeTabID, setActiveTabID] = useState("a");
//since this component will provide data to the child components, we will use DataContext.Provider
return (
<DataContext.Provider value={[activeTabID, setActiveTabID]}>
{props.children}
</DataContext.Provider>
);
}
export default TabSwitcher;
export { Tab, TabPanel };
And to use:
import TabSwitcher, { Tab, TabPanel } from "./TabSwitcher";
function App() {
return (
<div className="App">
<h1>TabSwitcher with Compound Components</h1>
<TabSwitcher>
<Tab id="a">
<button>a</button>
</Tab>
<Tab id="b">
<button>b</button>
</Tab>
<TabPanel whenActive="a">
<div>a panel</div>
</TabPanel>
<TabPanel whenActive="b">
<div>b panel</div>
</TabPanel>
</TabSwitcher>
</div>
);
}
export default App;
The problem is: they are using
TabPanel
as a container whereas I want a <div>
Solution 1:[1]
EDIT: Please see the link Alexander posted in the comment for the best practices in accessibility for tabs like this, or this link which has an example https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-2/tabs.html
My post here solves the problem in terms of "making this work", but you should really be aiming for proper tabs and tabpanels to make this accessible.
ORIGINAL ANSWER:
The problem is: they are using
TabPanelas a container whereas I want a<div>
When you say "I want a <div>", do you mean that you want to write div in your code, or that you want a div in the DOM?
The example you found, where they use TabPanel, does render a div to the DOM, because TabPanel returns a div.
One thing that jumps out is you don't need to store your Titles in state, because they are never changing.
To do what you're trying to do, I'd probably go like this:
import React,{useState} from "react";
const tabData = [
{title: "Section title 1", content: "Content of section 1"}
{title: "Section title 2", content: "Content of section 2"}
{title: "Section title 3", content: "Content of section 3"}
]
const MyTabsComponent = () => {
const [ct,setCt] = useState(null);
return (
<div className="tabs">
{tabData.map(({title}, i) => (
<TabButton
title={title}
isActive={i === ct}
onClick={()=>props.tabCt(i)} />
)}
<div class="view">
Content : {tabData[ct].content}
</div>
</div>
);
};
const TabButton = ({ title, isActive, onClick }) => {
return (
<button onClick={onClick} className={isActive ? "active-btn" : "btn"}>
{title}
</button>
);
};
export default MyTabsComponent;
Solution 2:[2]
From what I can tell, your question is how to render any number of tabs? It seems that your snippet already does this. However, you did have one too many parentheses, or one too few depending on how you look at it.
import React,{useState} from "react";
const myTabs = ['Section 1','Section 2','Section 3'];
const MyTabsComponent = () => {
const [ct,setCt] = useState(null);
return (
<div className="tabs">
<TabButtons tabCt={setCt} />
<div>Content: {ct}</div>
</div>
);
};
const TabButtons = (props) => {
return (
<>
{myTabs.map((item,i) => (
<button onClick={()=>props.tabCt(i)} className="btn">
{item}
</button>
)}
</>
);
};
export default MyTabsComponent;
This should work. You could have 1 or 1000 tabs in myTabs and it would render all of them.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |
