Simple tabs component in ReactJS - 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>
See visual
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>

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 TabPanel as 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;

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.

Related

Add dynamic link from an object in React

I'm new to React and I'm trying to insert a Link in a React Component. I made an object, and each item contains an external link. This the object :
export const myList =
[
{
"id":"P1",
"title":"Title1",
"description":"The first description",
"link":"https://random-link.io",
"cover":require("../img/myImg1.webp")
},
{
"id":"P2",
"title":"Title2",
"description":"The second description",
"link":"https://random-link2.io",
"cover":require("../img/my2ndImg.webp")
},
...
];
The main idea is to create pages for each item of the list, and a link to an external page to see more information.
I tried to do this :
function Page() {
const id = useParams();
const pageList = myList.find(list => list.id === id.id);
return(
<>
{
pageList ? (
<div className="Page">
<img className="ListCover" src={pageList?.cover} alt={pageList?.title}/>
<div className="information-list">
<span className="title-list">{pageList?.title}</span>
</div>
<div className="description-list">
<Dropdown titre="Description" description={pageList?.description} link={pageList?.link} />
</div>
</div>
) : <Navigate replace to="/404"/>
}
</>
)
}
export default Page;
In the Dropdown component, I made this :
function Dropdown({title, description, link}) {
const [open, setOpen] = useState(false);
return(
<div className="dropdown" id={`dropdown-${title}`}>
<div className="header-dropdown">
<div className="title-dropdown">{title}</div>
<span className={`arrow-dropdown ${open}`} onClick={() => setOpen(!open)}>
<img src={arrow} alt="Open it"/>
</span>
</div>
{
/* if dropdown is TRUE then it show the description */
open && <div className="description-dropdown">{description}
See more </div>
}
</div>
);
}
export default Dropdown;
The problem is that the link sends me to : http://localhost:3000/[object%20Object]; with another method I got http://localhost:3000/myProjet/https://random-link.io
I believe that the only issue in your code is that you are trying to use an object as the parameter for href which takes a string, try to just put link in there and it should work. it should look like:
<a href={link} rel='noreferrer'>

React change css style of a div in another component by button clicking in another component

on my Project I have a banner on top of my site with 2 buttons. when I click the button profile I want it to change the css style of a div in another component.
this is my code for the banner:
import Profile from "./Profile";
function Banner() {
const invis=false;
return (
<div className="banner">
<span className="bannerbtnsettings">
<button className="btnbannersettings">Settings</button>
</span>
<span className="bannerbtnprofile">
<button className="btnbannerprofile" onClick={Profile.changeStyle}>Profile</button>
</span>
</div>
);
}
export default Banner;
this is my code for the div in the other component:
import "../index.css";
import React, { useState } from "react";
const Profile = () => {
const [style, setStyle] = useState("profile-hidden");
const changeStyle = () => {
console.log("you just clicked");
setStyle("profile-displayed");
};
return (
<div>
<div className={style}> hellllo</div>
</div>
);
};
export default Profile;
I can only find information about this with parent-child components.
They said I should use a usestate import but I can't seem to get it working. what's the proper way to do this?
All you need is lift your state to parent component, if you have a long trip to your common ancestor you can try to use a context. Attached a working example. Hope it helps!
const Banner = ({ onClickHandler }) => {
return (
<div className="banner">
<span className="bannerbtnsettings">
<button className="btnbannersettings">Settings</button>
</span>
<span className="bannerbtnprofile">
<button className="btnbannerprofile" onClick={() => onClickHandler()}>Profile</button>
</span>
</div>
)}
const Profile = ({ style }) => {
return (
<div>
<div className={style}>I'm your profile :)</div>
</div>
);
};
const App = () => {
// We lift the state
const [style, setStyle] = React.useState("profile-hidden");
const profileHandler = () => {
setStyle(style === 'profile-hidden'
? 'profile-displayed'
: 'profile-hidden')
}
return(
<div>
<Banner onClickHandler={profileHandler} />
<Profile style={style} />
</div>
)
}
// Render
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
.profile-hidden {
display: none;
}
.profile-displayed {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
You cannot use this syntax for React Components COMPONENT.method
, in your case onClick={Profile.changeStyle} !
Instead you should make Banner parent component and use Profile component as child inside it or vise versa !
then You should pass the state style as props so then you will be able to use its value.
your code should look like this :
function Banner() {
const [style, setStyle] = useState("profile-hidden");
const changeStyle = () => {
console.log("you just clicked");
setStyle("profile-displayed");
};
return (
<div className="banner">
<span className="bannerbtnsettings">
<button className="btnbannersettings">Settings</button>
</span>
<span className="bannerbtnprofile">
<button className="btnbannerprofile" onClick={changeStyle}>Profile</button>
</span>
<Profile style={style} />
</div>
);
}
export default Banner;
and your Profile component :
const Profile = (props) => {
return (
<div>
<div className={props.style}> hellllo</div>
</div>
)
}

Simple tabs in ReactJS using components

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>
See visual
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>
UPDATED
Here is the latest implementation for you
function TabController({ children }) {
if (!children || children.length === 0) {
return null
}
const [currentTab, setCurrentTab] = React.useState(children[0])
return (
<>
{children.map((child) => (
<button key={child.title} onClick={() => setCurrentTab(child)}>
{child.props.title}
</button>
))}
<div className="view">{currentTab.props.children}</div>
</>
)
}
This is how we use it
<TabController>
<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 3</div>
<div title={'Section title 4'}>Content of section 4</div>
<div title={'Section title 5'}>Content of section 5</div>
</TabController>
OLD ANSWER
I tried to create a simple component for you. You can implement actual data for TAB_DATA and pass it into your MyTabsComponent as props
//I mocked TAB_DATA here for testing purpose
const TAB_DATA = [
{
title: 'Section 1',
content: 'Content 1',
},
{
title: 'Section 2',
content: 'Content 2',
},
{
title: 'Section 3',
content: 'Content 3',
},
]
const MyTabsComponent = () => {
const [ct, setCt] = useState(TAB_DATA[0].content) //set the first tab data as default
return (
<div className="tabs">
{TAB_DATA.map((tab) => (
<button
onClick={() => {
setCt(tab.content)
}}
>
{tab.title}
</button>
))}
<div className="view">{ct}</div>
</div>
)
}
If you want to create a component that has nested elements of an unknown number or type, then you need to make use of the children prop. This is not something you have to define, but represents the child elements of the component when it is called.
Simply nest any number of components or elements between your component tags, and in MyTabsComponent pull out the props and put the props.children inside your return statement where you'd like them to go.
For your example, I have elevated the state ct and title to the parent component of MyTabsComponent, and given that state directly to the rendered buttons. I also pass the ct to the MyTabsComponent so it can display its content.
MyTabsComponent
const MyTabsComponent = (props) => {
return (
<div className="tabs">
{props.children}
<div class="view">
Content : {props.ct}
</div>
</div>
);
};
The parent of MyTabsComponent
import React, { useState } from 'react'
const Main = () => {
const [title, setTitle] = useState(['Section 1','Section 2','Section 3']);
const [ct, setCt] = useState(null);
return (
<MyTabsComponent ct={ct}>
{title.map((item, i) =>
<button onClick={() => setCt(i)} className="btn">{item}</button>
)}
</MyTabsComponent>
)
}

why I'm getting two buttons with same style as previously imported on other component?

I have a function that is returning an array which contains 2 name and email , I understand that it will return both of them but I don't understand why it returns two buttons although I added single tag and also buttons appears with same style that I imported to the previous component(which has prop) , why ?
import React from 'react';
import './styles/contactlist.css';
const ContactList = (props) => {
const renderContactList = props.contacts.map(contact=>{
return(
<div className="item">
<div className="content">
<div>
<div className="name">{contact.name}</div>
</div>
<div>
<div className="mail">{contact.email}</div>
<button>Delete</button>
</div>
</div>
</div>
);
});
return(
<div className="celled-list">
{renderContactList}
</div>
);
}
export default ContactList;
and this is the component that has an array and prop
import React from 'react';
import './styles/App.css';
import Header from './Header';
import AddContact from './AddContact';
import ContactList from './ContactList';
function App() {
const contacts = [
{
id : 1,
name : "someone",
email: "something#yahoo.com"
},
{
id : 2,
name : "someone1",
email: "something#gmail.com"
}
]
return (
<div>
<Header/>
<AddContact/>
<ContactList contacts={contacts}/>
</div>
);
}
export default App;
Code Sandbox
You have 2 Delete buttons because you map your array to return an HTML which contains a button. Since you have 2 items in your contacts prop, you will have 2 buttons.
ContactList.js
const renderContactList = props.contacts.map((contact) => {
return (
{ /* . . . . //other elements */ }
<button>Delete</button> {/* this is your button */}
</div>
</div>
</div>
);
});
Now, if you want to have only 1 button, obviously you need to put it outside the .map function so it wont be included in the loop.
ContactList.js
const renderContactList = props.contacts.map((contact) => {
return (
{ /* . . . . //other elements */ }
{/* remove your button from here */}
</div>
</div>
</div>
);
});
return (
<div className="celled-list">
{renderContactList}
<button>Delete</button> {/* transfer your button here */}
</div>
);
Here's a working Code Sandbox.

adding and removing a classList in react js

I am using functional component in react js , in the below code
the class right-panel-active is not being added / is undefined. Someone help to enable the class be added when the button is toggled
import React from 'react';
import './style.css';
import {
Modal,
DropdownMenu
} from '../MaterialUI';
/**
* #author
* #function Header
**/
const Header = (props) => {
const container = () => {
document.getElementById('container');
}
const signUpButton = () => {
container.classList.add('right-panel-active');
};
const signInButton = () => {
container.classList.remove('right-panel-active');
};
return (
<div className="header">
<div className="container" id="container">
<button className="ghost" id="signIn" onClick={signInButton} >Sign In</button>
</div>
<div className="overlay-panel overlay-right">
<p>Enter your personal details and start journey with us</p>
<button className="ghost" id="signUp" onClick={signUpButton} >Sign Up</button>
</div>
</div>
)
}
export default Header
You are not utilising any of React's functionality.
Read about state management in React
and Event Handlers in React
const Header = (props) => {
const [isContainerActive, setIsContainerActive] = React.useState(false);
const signUpButton = () => {
setIsContainerActive(false);
};
const signInButton = () => {
setIsContainerActive(true);
};
return (
<div className="header">
<div id="container" className={`container${isContainerActive ? " right-panel-active" : ""}`}>
<button className="ghost" id="signIn" onClick={signInButton}>Sign In</button>
</div>
<div className="overlay-panel overlay-right">
<p>Enter your personal details and start journey with us</p>
<button className="ghost" id="signUp" onClick={signUpButton}>Sign Up</button>
</div>
</div>
);
}
ReactDOM.render(<Header />, document.getElementById("root"));
.header {height: 120px;}
.container {float:left;}
.overlay-right {display: none;background: red;float:right;height:100%;}
.right-panel-active ~ .overlay-right {display: inline-block;}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
PS: I also recommend https://www.npmjs.com/package/classnames , or the cx library for className management.
I think the best solution for this is to making a state and handle the style by a ternarian
function Welcome(props) {
const {name} = props;
return (
<h1 style={name === 'Sara' ? right-panel-active : right-panel-inactive}>Hello, {name}</h1>
)}
function App() {
return (
<div>
<Welcome name="Sara" />
</div>
);
}
This is the React way of doing,
You have to keep a local state (useState) and track the button click and based on the state change update the class to the container. You shouldn't directly change the DOM.
Click on the button to see the CSS is adding or not.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [isSignUp, setSignUp] = useState(true);
return (
<div id="container" className={isSignUp ? "right-panel-active" : ""}>
<button onClick={() => setSignUp(false)}>SignIn</button>
<button onClick={() => setSignUp(true)}>SignUp</button>
//for testing the change
{isSignUp ? "right-panel-active added" : "right-panel-active removed"}
</div>
);
}
Sample working code - https://codesandbox.io/s/fragrant-http-qo6du?file=/src/App.js
No need to add click event with vanilla js, you could add an React onClick event. You forgot to return the container.
// if you use curly braces you must return the value
const container = () => {
return document.getElementById('container');
};
// or skip the curly braces
const container = () =>
document.getElementById('container');

Resources