Add class to following buttons inside a map function - reactjs

I have a function that adds class onClick.
import * as React from 'react'
class ThisClass extends React.Component<any,any> {
constructor(){
super()
this.state = {active: ''}
this.ThisFunction = this.ThisFunction.bind(this)
}
const items = ['button1', 'button2', 'button3']
ThisFunction (i) {
this.setState({
active: i
})
}
render(){
return(
<div>
{
items.map((item, i) => {
return(
<button
onClick={()=>this.ThisFunction(i)}>{item}
className={`this_button ${this.state.active ? 'active' : ''}`}
{item}
</button>
)
})
}
</div>
)
}
}
export default ThisClass
What's suppose to happen:
<div>
<button class='this_button active'>button1</button>
<button class='this_button active'>button2</button>
<button class='this_button'>button3</button>
</div>
What's really happening:
<div>
<button class='this_button active'>button1</button>
<button class='this_button'>button2</button>
<button class='this_button'>button3</button>
</div>
I need to have a maximum of two active' class onClick function. Is there any way to get around this?

Change your state to hold an array of active indices and change the handler to only maintain the last two set (i.e. kick out the least recently used)
state = {
active: []
};
thisFunction = i => {
if (this.state.active.includes(i)) return; // already active!!
const active = [...this.state.active, i].slice(-2); // keep last 2
this.setState({ active });
};
You also had some syntax errors in the button rendering. The classname was being set as part of the button text. Set the active class if this.state.active array includes the active index
{["button1", "button2", "button3"].map((item, i) => {
return (
<button
onClick={() => this.thisFunction(i)}
className={`this_button ${
this.state.active.includes(i) ? "active" : ""
}`}
>
{item}
</button>
);
})}

There are some major errors in your code.
You are putting the className property as a child on the button, is an attribute instead
Every child on a list e.g your buttons needs to have a key
You are binding twice the function, one on the constructor and another one on the onClick event using an arrow function. Bind a function
class ThisClass extends React.Component {
constructor(){
super()
this.state = {active: ''}
}
ThisFunction (i) {
this.setState({
active: i
})
}
render(){
const items = ['button1', 'button2', 'button3']
return(
<div>
{
items.map((item, i) => {
return(
<button
key={i}
onClick={() => this.ThisFunction(i)}
className={`this_button ${this.state.active === i ? 'active' : ''}`}>
{item}
</button>
)
})
}
</div>
)
}
}
ReactDOM.render(
<ThisClass />,
document.body
);
<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>

Related

Handle multiple child component in React

I've tried to look everywhere and couldn't find anything related to my use case, probably I'm looking for the wrong terms.
I have a situation where I have a bar with 3 icons, I'm looking for set one icon "active" by changing the class of it.
The icon is a custom component which have the following code
export default class Icon extends Component {
state = {
selected : false,
}
setSelected = () => {
this.setState({
selected : true
})
}
setUnselected = () => {
this.setState({
selected : false
})
}
render() {
var classStatus = '';
if(this.state.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return <div className={classStatus} onClick={this.props.onClick}><FontAwesomeIcon icon={this.props.icon} /></div>
}
}
In my parent component I have the following code
export default class MainPage extends Component {
handleClick(element) {
console.log(element);
alert("Hello!");
}
render() {
return (
<div className="wrapper">
<div className="page-header">
<span className="menu-voice">File</span>
<span className="menu-voice">Modifica</span>
<span className="menu-voice">Selezione</span>
</div>
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon icon={faFileCode} onClick={() => this.handleClick(this)} />
<Icon icon={faCodeBranch} onClick={() => this.handleClick(this)} />
<Icon icon={faMagnifyingGlass} onClick={() => this.handleClick(this)} />
</div>
</span>
<span className="files-section">Files</span>
<span className="editor-section"></span>
</div>
<div className="page-footer">
Footer
</div>
</div>
);
}
}
What I'm trying to achieve is that when one of the Icon child component get clicked it will set the selected state to true manage by the parent component, in the same time while one of them is true I would like that the parent would set to false the other twos.
I've tried to use the useRef function but it doesn't look as a best practise.
Which is the correct way to do it? Sending also this to the handleClick function it just return the MainPage class instead of the child. Any suggestion at least where I should watch?
Thanks in advance
I suggest not storing the state in the icon, since it doesn't know what else you're using it for. Simply have the icon component take it's 'selected' status from props. e.g.
export default class Icon extends Component {
render() {
var classStatus = '';
if(this.props.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return (
<div className={classStatus} onClick={this.props.onClick}>.
<FontAwesomeIcon icon={this.props.icon} />
</div>
);
}
};
Then you can just manage the state in the parent where it should be:
export default class MainPage extends Component {
constructor(props) {
super(props);
this.state = { selectedOption : '' };
}
handleSelectOption(newValue) {
this.setState({ selectedOption: newValue });
}
isSelected(value) {
return value === this.state.selectedOption;
}
render() {
return (
<div className="wrapper">
{ /* etc... */ }
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon
icon={faFileCode}
onClick={() => this.handleSelectOption("File")}
selected={isSelected("File")}
/>
<Icon
icon={faCodeBranch}
onClick={() => this.handleSelectOption("Modifica")}
selected={isSelected("Modifica")}
/>
{ /* etc... */ }
</div>
</span>
</div>
{ /* etc... */ }
</div>
);
}
};
You should define a constructor in your class component:
constructor(props) {
super(props);
this.state = { selected : false };
}
You also have to call a function which modify the state when you click on the Icon. onClick={this.props.onClick} doesn't change the state

Hide all div and show one div on clicking multiple button

I am trying to fit 3 component in a single page by hiding/showing on a div.But I am not really getting into how to do it.This is the first div.
<div>
<p>What is the type of your property?</p>
<button >Residence</button>
<button>Commercial</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
Only If i click the 'Commercial' or 'Next' button it would go into the second div and first div will hide.
<div>
<p>What is the type of your commercial property?</p>
<button>Office</button>
<button>Restaurant</button>
<button >Outlet</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
and lastly if i click 'restaurant' button from the first div and any button of the second div except the back button it will go into the third div and other div will hide.this is the third div.
<div>
<div className='slider' style={{ marginTop:'165px',marginLeft:'319px',width:'700px',backgroundColor:'EF5350'}} >
<Slider min={850} max={5000} value={value} onChangeStart={this.handleChangeStart}
onChange={this.handleChange}
onChangeComplete={this.handleChangeComplete}
/>
<div className='value'>{value} Squarefeet</div>
<div style={{marginTop:'86px'}}>
<span onChange={this.handleChange} onClick={() => this.saveValue()} >Next</span>
<span onChange={this.handleChange} onClick={() => this.saveValue()} >Next</span>
</div>
</div>
</div>
I tried to do it this way. But it will not work.
import React from 'react';
import Link from "next/link";
class Jh extends React.Component {
constructor() {
super();
this.state = {
shown: true,
hide: false
};
}
toggle() {
this.setState({
shown: !this.state.shown
});
}
toggles() {
this.setState({
shown: !this.state.hide
});
}
render() {
var shown = {
display: this.state.shown ? "block" : "none"
};
var hidden = {
display: this.state.shown ? "none" : "block"
}
return (
<div>
<button onClick={this.toggle.bind(this)} style={ shown }>
<div>
<p>What is the type of your property?</p>
<button >Residence</button>
<button>Commercial</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
</button>
<button onClick={this.toggles.bind(this)} style={ hidden }>
<div>
<p>What is the type of your commercial property?</p>
<button>Office</button>
<button>Restaurant</button>
<button >Outlet</button>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Back</span>
<span style={{background:'transparent', border:'0', fontSize:'16px',color:'#ef3530'}}>Next</span>
</div>
</button>
</div>
)
}
}
export default Jh
What should be my approach?
There are many patterns to achieve a "switch case", I'll try to show my favorites:
For sipmlicity, I'll use a generic use case.
Straight Forward
Managing visible state for every component:
return {visible && <CoolComponent id={1} />};
Switch case in disguise
Manage a state of object keys. (currentCounter)
const countersPicker = {
counter1: <Counter id={1} />,
counter2: <Counter id={2} />,
coolComponent: <CoolComponent id={3} />
};
return {countersPicker[currentCounter]};
Here you also can take action on the object, for example, adding a header:
return {Object.entries(countersPicker).map(([key,component]) =>
<div key={key}>
<h1>Component key = {key}</h1>
{component}
</div>
)};
Filter Children
Manage a predicate and use it for filtering/mapping the children.
Check React.Children API.
return (
<FilterComponents predicate={predicate}>
<Counter key={1} id={1} />
<Counter key={2} id={2} />
<CoolComponent key={3} id={3} />
<BestComponent key={4} id={4} />
</FilterComponents>
);
function FilterComponents({ children, predicate }) {
const filteredChildren = React.Children.toArray(children).filter(child =>
// Use the predicate.
// Filter a child by key, key & type or even use ref etc.
);
return <div>{filteredChildren}</div>;
}
I believe you are looking for something like this.
Main things to-do:
Enhance your state-value. Keep track of the different pages in sequence by using an array. Track the current page. Track the start and end of the collection.
Here is the sandbox as well: https://codesandbox.io/s/unruffled-sun-gpzx6
import React from "react";
class Pages extends React.Component {
state = {
currentPage: "property",
pages: ["property", "type", "firstBusiness"],
start: true,
end: false
};
changePage = event => {
const { currentPage, pages } = this.state;
const { name } = event.target;
//check if we are going to end
if (
name == "next" &&
pages[pages.indexOf(currentPage) + 1] === pages[pages.length - 1]
) {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
end: true,
start: false
});
//go to next page
} else if (name == "next") {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
start: false
});
//check if we are going to beginning
} else if (
name == "back" &&
currentPage !== pages[0] &&
pages[pages.indexOf(currentPage) - 1] == pages[0]
) {
this.setState({
currentPage: pages[pages.indexOf(currentPage) - 1],
start: true
});
//go back one page
} else {
this.setState({
currentPage: pages[pages.indexOf(currentPage) - 1],
end: false
});
}
};
goToNextPage = () => {
const { currentPage, pages, end } = this.state;
//check if we are going to end
if (pages[pages.indexOf(currentPage) + 1] === pages[pages.length - 1]) {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
end: true,
start: false
});
//go to next page
} else if (end) {
return;
} else {
this.setState({
currentPage: pages[pages.indexOf(currentPage) + 1],
start: false
});
}
};
render() {
const { currentPage, start, end } = this.state;
return (
<div style={{ background: "gray" }}>
{currentPage === "property" ? (
<div>
<p>What is the type of your property?</p>
<button onClick={this.goToNextPage}>Residence</button>
<button onClick={this.goToNextPage}>Commercial</button>
</div>
) : null}
{currentPage === "type" ? (
<div>
<p>What is the type of your commercial property?</p>
<button onClick={this.goToNextPage}>Office</button>
<button onClick={this.goToNextPage}>Restaurant</button>
<button onClick={this.goToNextPage}>Outlet</button>
</div>
) : null}
{currentPage === "firstBusiness" ? (
<div>
<p>Is this your first business?</p>
<button onClick={this.goToNextPage}>Yes</button>
<button onClick={this.goToNextPage}>No</button>
</div>
) : null}
<div>
<button onClick={this.changePage} name="back" disabled={start}>
Back
</button>
<button onClick={this.changePage} name="next" disabled={end}>
Next
</button>
</div>
</div>
);
}
}
export default Pages;
So essentially you want router like functionality. Here is one approach:
class FirstPage extends React.Component {
render() {
//...first page content
}
}
class SecondPage extends React.Component {
render() {
//...second page content
}
}
const pages = {
first: FirstPage,
second: SecondPage
};
class App extends React.Component {
constructor() {
this.state = {
page: 'first'
};
}
render() {
const PageComponent = pages[this.state.page];
return <div>
<button onClick={() => this.setState({page: 'first'})}>First page</button>
<button onClick={() => this.setState({page: 'second'})}>Second page</button>
<PageComponent/>
</div>
}
}
There are many ways to solve this problem. But in my opinion the best solution is the one which solves the problem in a succinct manner.
Please find below the working solution which I have tried and works like a charm:
import React from "react";
class Pages extends React.Component {
state = {
activeTab: 1
};
toggle = tab => {
this.setState({
activeTab: tab
});
};
togglePage = page => {
if (page === "next") {
this.setState({
activeTab: this.state.activeTab + 1
});
} else if (page === "back") {
this.setState({
activeTab: this.state.activeTab - 1
});
}
};
render() {
return (
<div style={{ background: "#dedede" }}>
<div hidden={this.state.activeTab === 1 ? false : true}>
<p>1) What is the type of your property?</p>
<button class="btn btn-primary" onClick={() => this.toggle(2)}>
Residence
</button>
<button onClick={() => this.toggle(2)}>Commercial</button>
</div>
<div hidden={this.state.activeTab === 2 ? false : true}>
<p>2) What is the type of your commercial property?</p>
<button onClick={() => this.toggle(3)}>Office</button>
<button onClick={() => this.toggle(3)}>Restaurant</button>
<button onClick={() => this.toggle(3)}>Outlet</button>
</div>
<div hidden={this.state.activeTab === 3 ? false : true}>
<p>3) Is this your first business?</p>
<button onClick={this.NextAction}>Yes</button>
<button onClick={this.NextAction}>No</button>
</div>
<div>
<button
onClick={() => this.togglePage("back")}
name="back"
disabled={this.state.activeTab === 1 ? true : false}
>
Back
</button>
<button
onClick={() => this.togglePage("next")}
name="next"
disabled={this.state.activeTab === 3 ? true : false}
>
Next
</button>
</div>
</div>
);
}
}
export default Pages;
In react we have a hidden attribute which you can use to show/hide the elements without having to write any css for the same.
And I have tried to solve the problem with the least number of variables.
The sandbox for the same can be found here : https://codesandbox.io/s/mysolution-g8fu6
Hope this helps!

onClick event on an array element javascript React

I need to do a collapsible section with a list of disciplines. The disciplines are stored in an array. I wrote an onClick event but except one discipline which I clicked, I get all of them slid down. How can I apply the event to each element, so I can decide which one will be slide down?
export default class Predictions extends React.Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
this.state={
display: 'block',
};
}
handleClick(e) {
this.setState({
display: this.state.display === 'none' ? 'block' : 'none',
});
console.log('click', e);
};
render() {
return (
<section className="l-section c-predictions" >
<h2 className="header" >Predictions</h2>
<div className="content">
{this.props.disciplines.map((discipline, index) => {
return (
<div onClick={event => this.handleClick(discipline.id, event)} key={discipline.name} className="c-discipline">
<span className="name">{discipline.name}</span> - <span className="score">{disciplineScore(this.props.athlete.skillset, discipline.requirements)}</span>
<div style={this.state} className="element">
<p>{discipline.tags !== undefined ? discipline.tags.toString().replace(',', ', ') : ''}</p>
<p className="isIndividual">{discipline.isIndividual===true ? "Individual sport" : "Team sport"}</p>
<img src={discipline.photo}/>
</div>
</div>
)
})}
</div>
</section>
)
}
}
You need a way to identify which element has been clicked.
Here's an example:
export default class App extends React.Component {
state = {
opened: true,
selected: ''
};
toggleHidden = key => {
this.setState({ opened: !this.state.opened, selected: key });
};
render() {
return (
<div>
{arr.map((s, i) => (
<div key={i}>
<p>{s}</p>
<button onClick={() => this.toggleHidden(i)}>Toggle</button>
{!this.state.opened && this.state.selected === i && <h1>{s}</h1>}
</div>
))}
</div>
);
}
}

How to implement a React container which can handle all events from its sub-components?

Assume that the container contains 3 buttons:
<Container>
<Button1/> <Button2> <Button3/>
</Container>
By default, all buttons are in 'Normal' state. If I click on Button1, I'd like to have Button1 becomes 'Active' state (with solid background) and Button2 & Button3 become 'Inactive' state (with gray border).
What's the best way to achieve that in React?
Name is a valid html attribute to use in buttons. If you want to use React to control this, you can simply have a state property attributed to the selected button's name. With that state, you can control the disabled attribute, as well as the class name.
this.state = {
activeButton: null
};
onButtonClick = (e) => {
const { name } = e.target;
this.setState({
activeButton: name
});
};
render() {
const { activeButton } = this.state;
return (
<Component>
<button
className={`${activeButton === 'one' && 'active' || ''}`}
onClick={this.onButtonclick}
name="one"
disabled={activeButton !== 'one'}>
Button 1
</button>
<button
className={`${activeButton === 'two' && 'active' || ''}`}
onClick={this.onButtonclick}
name="two"
disabled={activeButton !== 'two'}>
Button 2
</button>
<button
className={`${activeButton === 'three' && 'active' || ''}`}
onClick={this.onButtonclick}
name="three"
disabled={activeButton !== 'one'}>
Button 2
</button>
</Component>
);
}
You can consider using button as a child component
class Button extends React.Component {
constructor(props) {
super(props);
this.setActive = this.setActive.bind(this);
}
setActive() {
this.props.onClick(this.props.value);
}
render() {
return (<button onClick={this.setActive} className={this.props.active ? 'active' : ''}>
{this.props.children}
</button>);
}
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
active: 0,
};
this.buttons = Array.from({ length: 5 }).map((v, index) => index);
this.setActive = this.setActive.bind(this);
}
setActive(active) {
this.setState({ active })
}
render() {
return (<div>
{
this.buttons.map((value) => (<Button key={value} value={value} active={this.state.active === value} onClick={this.setActive}>Button #{value}</Button>))
}
</div>);
}
}
Working Demo
Make your component look like below, this will work for sure:
class App extends React.Component {
constructor(props) {
super(props)
this.setActiveBackground = this.setActiveBackground.bind(this);
this.state = {
selectedCircle: {
},
};
}
setActiveBackground(name, event) {
let selected = this.state.selectedCircle;
selected = {};
selected[name] = this.state.selectedCircle[name] == "selected" ? "" : "selected";
this.setState({selectedCircle: selected});
}
render() {
return (
<div className="container">
<div className="row">
<div className="card-panel white">
<div className="center">
<p>Set Active background in selected button</p>
<button onClick={this.setActiveBackground.bind(this, "first")} className={this.state.selectedCircle["first"]}>Button 1</button>
<button onClick={this.setActiveBackground.bind(this, "second")} className={this.state.selectedCircle["second"]}>Button 2</button>
<button onClick={this.setActiveBackground.bind(this, "third")} className={this.state.selectedCircle["third"]}>Button 3</button>
</div>
</div>
</div>
</div>
);
}
}
Check demo for output

reactjs map and css class toggle

<div>
{this.props.data.map((res, index) => {
return (<div key={index}>
<div>
<span>{response.testData}</span>
<a key={index} onClick={() => this.showExtraLine(index)}><span className={`btn-green ${this.state.showExtraLine ? 'active' : ''}`}></span></a>
{ this.state.showExtraLine ? <span>
{res.abc[0].def}
</span> : '' }
</div>
</div>
);
})}
</div>
showExtraLine(e){
this.setState({
showExtraLine: !this.state. showExtraLine,
});
}
Need to toggle the {res.abc[0].def} part on click of anchor - toggle works, but not sure how to handle toggling only the corresponding span - right now it is hiding all the rows..how to handle css toggle when using .map?
I think the problem is in your state variable, you are using a single state variable and printing the <span> on the basis of state of that variable.
Instead of that use an array in state showExtraLine = [],
in showExtraLine() function you are passing index, use that index to toggle only that element.
Try this it should work:
{this.props.data.map((res, index) => {
return (<div key={index}>
<div>
<span>{response.testData}</span>
<a key={index} onClick={() => this.showExtraLine(index)}><span className={`btn-green ${!this.state.showExtraLine[index] ? 'active' : ''}`}></span></a>
{ !this.state.showExtraLine[index] ?
<span>{res.abc[0].def}</span>
: '' }
</div>
</div>
);
})}
showExtraLine(index){
let showExtraLine = this.state.showExtraLine.slice(0);
showExtraLine[index] = !showExtraLine[index];
this.setState({
showExtraLine: showExtraLine,
});
}
Right now you are maintaining the state for all of the mapped elements in your component, so they all reference the same value. You should instead create a component that will be used to render each of your mapped elements individually with their own state.
class Parent extends React.Component {
render() {
return (
<div>
{this.props.data.map((res, index) => <Child key={index} {...res} />)}
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
showExtraLine: false
};
this.showExtraLine = this.showExtraLine.bind(this);
}
render() {
return (
<div>
<div>
<span>{this.props.testData}</span>
<a key={index} onClick={this.showExtraLine}>
<span className={`btn-green ${this.state.showExtraLine ? 'active' : ''}`}></span>
</a>
{ this.state.showExtraLine ? <span>{this.props.abc[0].def}</span> : '' }
</div>
</div>
);
}
showExtraLine(e){
this.setState({
showExtraLine: !this.state.showExtraLine
});
}
}

Resources