One component in every view but with different className - reactjs

So, I have a progress bar for my application that I have to put it in every other component with different class name to change the color and show the progress of the process.
<ol>
<li className="complete" data-step="1">
</li>
<li className="active" data-step="2">
</li>
<li data-step="3">
</li>
</ol>
Is there a more proper way to prevent copying the bar in every component and just change the class names based on the component it is in?

You can simply make conditional rendering
Example:
<ol>
<li className={condition ? "active" : "complete"} data-step="1">
</li>
<li className={condition ? "active" : "complete"} data-step="2">
</li>
<li data-step="3">
</li>
</ol>

I would do something like this:
const completedStep = 2; // 1, 2, 3 (0 == nothing is completed)
const steps = ['Step 1', 'Step 2', 'Step 3']; // can be whole component, not just a string
return (
<ol>
{steps.map((step, index) => {
return (
let className;
if (index < completedStep) {
className = 'completed';
} else if (index === completedStep) {
className = 'active';
} else {
className = '';
}
<li
key={index}
data-step={index + 1}
className={className}
>
{step}
</li>
);
})}
</ol>
);
The parameters above can be passed down as props to the component.

Related

How to map an array that is inside another in react

I am having trouble with array mappings. There is an array with data I would like to bind to my component. It looks like the following:
export var arrayObjects: IObjects[] = [
{
name: 'First Object',
icon: 'bi bi-capsule',
subMenu: [
{
name: 'First Sub Object',
path: ''
},
{
name: 'Second Sub Object',
path: ''
}
]
}
]
The array's type is an interface IObjects which contais name, icon, path and subMenu: IObjects. I need to access the subMenu items in order to create a dynamic menu. Althought, everytime I try to map and call the objects there is no return.
Below you'll be able to see how the mapping I made goes:
<ul>
{ arrayNavCadastros.map((item:any, subMenu: any) =>(
<li className="nav-item">
<i className={item.icon}></i>
{item.name}
<ul>
{ subMenu.map((name:any) =>(
{name}
))}
</ul>
</li>
)) }
</ul>
I also tried doing something like {item.subMenu.name} but there is also no return.
I'm new to react but I'm used to do these kind of bindings in Angular, which seems way easier since you should only do a loop inside the other subMenu from the arrayObjects... I would appreaciate any help!
Note: when I map the first properties of the arrayObjects (which, from the example, returns 'First Object') it works as well as all the titles for the sub menus.
so you have array in subMenu and in each iterate you have object of element
so you need to change this :
{ subMenu.map((name:any) =>(
{name}
))}
to this :
{ subMenu.map((sub:any) =>(
{sub.name}
))}
Few pointers
provide a key for items in the array
<ul>
{ arrayNavCadastros.map((item:any, subMenu: any) =>(
<li
key={item.id} // key for React to know which item to udpate, when array updated
className="nav-item"
>
<i className={item.icon}></i>
{item.name}
<ul>
{ subMenu.map((item:any, idx: number) =>(
<li key={idx}> // key and might need li
<a href={item.path}>{item.name}</a>
</li>
))}
</ul>
</li>
)) }
</ul>
or you can create a child component
<ul>
{ arrayNavCadastros.map((item:any, subMenu: any) =>(
<li
key={item.id} // key for React to know which item to udpate, when array updated
className="nav-item"
>
<i className={item.icon}></i>
{item.name}
<SubMenu items={subMenu} />
</li>
)) }
</ul>
//child component
const SubMenu = ({ items = []}) => {
return (
<ul>
{ subMenu.map((item:any, idx: number) =>(
<li key={idx}> // key and might need li
<a href={item.path}>{item.name}</a>
</li>
))}
</ul>
)
}
New React documentation has helpful information on working with array
Hope it helps

onClick triggering all sub menus instead of only the clicked one

When I click on an item it should expend some sub items. This is working but if I have two, three or four etc. list items then when I click on one it expands ALL of the sub items for all the list items which is obviously not what I want. How can I fix this code to make it only open expand the one I actually clicked on?
const [sideActive, setSideActive] = useState(false);
const toggleSideActive = () => {
setSideActive(!sideActive);
};
html:
<li>
<div
onClick={toggleSideActive}
className={
sideActive
? `${styles.navList__subheading} row ${styles.row__align_v_center} ${styles.navList__subheading__open}`
: `${styles.navList__subheading} row ${styles.row__align_v_center}`
}
>
<span className={styles.navList__subheading_icon}>
<FaBriefcaseMedical />
</span>
<span className={styles.navList__subheading_title}>
insurance
</span>
</div>
<ul
className={
sideActive
? `${styles.subList}`
: `${styles.subList} ${styles.subList__hidden}`
}
>
<li className={styles.subList__item}>medical</li>
<li className={styles.subList__item}>medical</li>
<li className={styles.subList__item}>medical</li>
</ul>
</li>
You can create a local state for tracking the selected id and show the content based on the state. Also, update the selected Id on click of the tab like below.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [selected, setSelected] = useState("");
const data = [
{
id: 1001,
name: "Tab - 1",
content: ["test1", "test2"]
},
{
id: 1002,
name: "Tab - 2",
content: ["test21", "test22"]
}
];
return (
<div className="App">
<ul class="parent">
{data.map((v) => (
<li onClick={() => setSelected(selected !== v.id ? v.id : "")}>
{v.name}
{selected === v.id && (
<ul class="content">
{v.content.map((val) => (
<li>{val}</li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
);
}
For the below example, click on the tab to see the content.
Working code - https://codesandbox.io/s/long-leaf-cl4cy?file=/src/App.js:0-759
Let me know if you are facing any issues.

Add a class and remove class from another in ReactJS

I want to create a tab using ul and li and the tab will be shown the same content which is selected and should be show a button which is send me to the next tab and that will be active for indication which tab is showing to me
render() {
return (
<div>
<ul>
<li>Tab 1</li>
<li>Tab 2</li>
<li>Tab 3</li>
</ul>
<div id="tab-menu-1">Tab 1 Content <button>Move to Tab 2</button></div>
//in default it should show the tab 1 content only and When i Press this button on above Div it should be moved to next tab and the class name will be transfer to the tab 2 on <li>
<div id="tab-menu-2">Tab 2 Content <button>Move to Tab 2</button></div>
//in default it should show the tab 2 content only and When i Press this button on above Div it should be moved to next tab and the class name will be transfer to the tab 3 on <li>
<div id="tab-menu-3">Tab 3 Content <button>Move to Tab 2</button></div>
//in default it should show the tab 3 content only and When i Press this button on above Div it should be moved to next tab and the class name will be transfer to the tab 1 on <li>
</div>
);
}
You can save on the state of the component the activeIndex id, and render conditionally the className.
class Demo extends React.Component {
state = {
activeIdx: 0
};
render() {
return (
<div>
<ul>
<li><a href="#tab-menu-1" className={this.state.activeIdx === 0 ? 'active' : ''} data-index={0} onClick={this.handleChange}>Tab 1</a>
</li>
<li><a href="#tab-menu-2" className={this.state.activeIdx === 1 ? 'active' : ''} data-index={0} onClick={this.handleChange}>Tab 2</a></li>
<li><a href="#tab-menu-3" className={this.state.activeIdx === 2 ? 'active' : ''} data-index={0} onClick={this.handleChange}>Tab 3</a></li>
</ul>
<div id="tab-menu-1">Tab 1 Content <button>Move to Tab 2</button></div>
<div id="tab-menu-2">Tab 2 Content <button>Move to Tab 2</button></div>
<div id="tab-menu-3">Tab 3 Content <button>Move to Tab 2</button></div>
</div>
);
}
handleChange = (evnt) => {
this.setState({activeIdx: evnt.target.data.index});
}
}
A better approach would be to generate the list of items out of an array.
class Demo extends React.Component {
state = {
activeIdx: 0,
list: ["content 1", "content 2", "content 3"]
};
render() {
return (
<div>
<ul>
{this.state.list.map((item, index) => (
<li key={item}>
<a
href={`#tab-menu-${index + 1}`}
className={this.state.activeIdx === index && "active"}
data-index={index}
onClick={this.handleChange}
>
Tab {index + 1}
</a>
</li>
))}
</ul>
{this.state.list.map((item, index) => (
<div id={`tab-menu-${index + 1}`} key={item}>
{item}
<button>Move to Tab {index + 2}</button>
</div>
))}
</div>
);
}
handleChange = evnt => {
this.setState({ activeIdx: evnt.target.data.index });
};
}

Adding a CSS class to a JSX in ReactJS

I wanted to add an 'active' class to a menu element, written in ReactJS. I tried doing it with the conventional JS method, but it failed. A click on any <li> tag, should result is removal of the 'active' class from all the <li>, and retain/ add it only to the one list tag in which the click was triggered.
Note: I know it may seem very naive on my part, but I'm just starting with ReactJS. Please ignore the stupidity.
import React, { Component } from 'react';
class Sidebar extends Component{
render(){
return(
<div className="sidebarContainer p-2">
<div className="mainMenu">
<ul className="levelOne pl-0">
<li className="mb-3 pl-2 menuTitle active" id="MenuTitle1">
...
</li>
<li className="mb-3 pl-2 menuTitle" id="MenuTitle2" onClick={this.clickMenu.bind(this,'MenuTitle2')}>
...
</li>
<li className="mb-3 pl-2 menuTitle" id="MenuTitle3" onClick={this.clickMenu.bind(this,'MenuTitle3')}>
...
</li>
</ul>
</div>
</div>
);
}
clickMenu(id){
// Add class 'active' on the clicked <li>, and remove from all other <li>
}
}
export default Sidebar;
I saw a similar question here, but that couldn't help me.
Idea is, store the id of clicked item in state variable and put the check with className. If item's id is same as state value then only assign the className active.
Write it like this:
class Sidebar extends Component{
constructor() {
super()
this.state = {
activeItem: 'MenuTitle1'
}
}
clickMenu(id){
// Add class 'active' on the clicked <li>, and remove from all other <li>
this.setState({
activeItem: id,
})
}
getClassName(id) {
if(id === this.state.activeItem) return 'mb-3 pl-2 menuTitle active'
return 'mb-3 pl-2 menuTitle'
}
render(){
return(
<div className="sidebarContainer p-2">
<div className="mainMenu">
<ul className="levelOne pl-0">
<li
id="MenuTitle1"
className={this.getClassName('MenuTitle1')}
onClick={this.clickMenu.bind(this,'MenuTitle1')}>
...
</li>
<li
id="MenuTitle2"
className={this.getClassName('MenuTitle2')}
onClick={this.clickMenu.bind(this,'MenuTitle2')}>
...
</li>
<li
id="MenuTitle3"
className={this.getClassName('MenuTitle3')}
onClick={this.clickMenu.bind(this,'MenuTitle3')}>
...
</li>
</ul>
</div>
</div>
);
}
}
You can maintain the state for clicked menu item:
clickMenu(id){
this.setState({activeMenu: id})
}
Then, define className like this:
className={
this.state.activeMenu == id {/* eg. "MenuTitle1" */}
? 'mb-3 pl-2 menuTitle active'
: 'mb-3 pl-2 menuTitle'
}
Like Bhojendra suggested store datas linked to your display inside your state then when you want to update the display of your component use the method setState, this will trigger render again (react style).
import React, { Component } from 'react';
import ReactDOM from "react-dom";
class Sidebar extends Component {
constructor() {
super();
this.state = {
activeMenuId: "MenuTitle1"
};
}
render() {
return (
<div className="sidebarContainer p-2">
<div className="mainMenu">
<ul className="levelOne pl-0">
<li className={`mb-3 pl-2 menuTitle ${this.state.activeMenuId === "MenuTitle1" ? "active" : ""}`} id="MenuTitle1" onClick={this.clickMenu.bind(this, 'MenuTitle1')}>
1
</li>
<li className={`mb-3 pl-2 menuTitle ${this.state.activeMenuId === "MenuTitle2" ? "active" : ""}`} id="MenuTitle2" onClick={this.clickMenu.bind(this, 'MenuTitle2')}>
2
</li>
<li className={`mb-3 pl-2 menuTitle ${this.state.activeMenuId === "MenuTitle3" ? "active" : ""}`} id="MenuTitle3" onClick={this.clickMenu.bind(this, 'MenuTitle3')}>
3
</li>
</ul>
</div>
</div>
);
}
clickMenu(id) {
// Add class 'active' on the clicked <li>, and remove from all other <li>
this.setState({activeMenuId: id});
}
}
export default Sidebar;
ReactDOM.render(<Sidebar />, document.body);
Another way of just make using initialstate and setState.
import React, { Component } from "react";
class Sidebar extends Component {
constructor(props) {
super(props);
this.initialState = {
MenuTitle1: "active",
MenuTitle2: "",
MenuTitle3: ""
};
this.state = this.initialState;
}
render() {
return (
<div className="sidebarContainer p-2">
<div className="mainMenu">
<ul className="levelOne pl-0">
<li
className={`mb-3 pl-2 menuTitle ${this.state.MenuTitle1} `}
id="MenuTitle1"
onClick={this.clickMenu.bind(this, "MenuTitle1")}
>
one
</li>
<li
className={`mb-3 pl-2 menuTitle ${this.state.MenuTitle2}`}
id="MenuTitle2"
onClick={this.clickMenu.bind(this, "MenuTitle2")}
>
two
</li>
<li
className={`mb-3 pl-2 menuTitle ${this.state.MenuTitle3}`}
id="MenuTitle3"
onClick={this.clickMenu.bind(this, "MenuTitle3")}
>
three
</li>
</ul>
</div>
</div>
);
}
clickMenu(id) {
this.setState(this.initialState);
this.setState({
[id]: "active"
});
}
}
export default Sidebar;

react dom traverself or parent div targeting?

I have the following in React (using redux):
....
deleteBase(title) {
this.props.dispatch(removeBase(title));
}
render() {
const baseList = this.props.bases.map(base => {
return (
<li key={base.title} className="base">
<Link to={base.title}>{base.title}</Link>
<i
className="fas fa-times"
onClick={title => this.deleteBase(title)}
/>
</li>
);
});
}
In the dispatch function of removeBase I will need to have access to the single base.title from the clicked element. No matter how I try to get the right title transferred over to the function I can't do it.
return (
<ul className="baseWrapper">
{baseList}
<li className="add-list-wrapper">
<AddBase type="base" onAdd={title => this.addBase(title)} />
</li>
</ul>
);

Resources