I have a strange - at least for me :-) - issue with my Components.
I have a map() function generating a list of Child elements. When I change the filter and the list repopulates the state of the children remain as it was. For example, if the second Alumni Child Component has state {height:auto} the new second Allumni Child has again {height: auto}.
I find it really odd because this is not the same element, it's a new element corresponding to another person, with new content passed through props.
Long story short how do I force my new Child elements to have initial state {height: 0}?
There is a lot of details hidden but the core of my Parent App is as follows:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
filterName: "",
filterInitial: "Α",
....
filteredGraduates:[],
loaderVisible:true
};
this.updateInitial = this.updateInitial.bind(this)
...
}
componentDidMount(){
...
}
updateInitial(letter) {
if (letter==='*'){
this.setState({
filterInitial: "*",
filteredGraduates : [...graduates]
})
}else{
this.setState({
filterInitial: letter,
filteredGraduates : graduates.filter( (graduate) => graduate.Surname.charAt(0)===letter )
})
}
console.log('this is the letter: ',letter);
}
render() {
return (
<div className="app">
<div className="filters">
<div className="name-pagination">
<span
onClick={ () => this.updateInitial('*')}
className={'*'==this.state.filterInitial? 'active': ''}
>Α - Ω
</span>
<hr/>
{letters.map( (letter) => {
return(
<span
onClick={ () => this.updateInitial(letter)}
className={letter==this.state.filterInitial? 'active': ''}
>{letter}</span>
)
}
)}
</div>
</div>
</div>
{this.state.loaderVisible && <Loader /> }
{!this.state.loaderVisible && <div className="graduates-wrapper">
{this.state.filteredGraduates
.sort( (x,y) => x.Surname.localeCompare(y.Surname) )
.map( (graduate) => {
return(
<div>
<Allumni
key={graduate.id}
.... a lot of props ...
/>
</div>
)
})
}
</div>}
</div>
);
}
}
and my Child is:
class Allumni extends React.Component {
constructor(props) {
super(props);
this.state = {
height:0
};
this.updateHeight = this.updateHeight.bind(this)
}
updateHeight() {
this.setState({
height: this.state.height === 0 ? 'auto' : 0,
});
};
render() {
const {
name,
surName,
.......
} = this.props
return (
<div className="allumni-wrapper">
<div className="allumni-main-info">
<span className="allumni-surname">{surName}</span><br/><span className="allumni-name">{name}</span>
</div>
<div className="allumni-extra">
<span className="allumni-year">{yearOf}</span><br/>
<span className="allumni-job">{job}</span> / <span className="allumni-home-city">{homeCity}</span>
</div>
<div className="allumni-details">
<AnimateHeight
duration={ 500 }
height={ this.state.height }
>
<p><span className="attribute-name">... {fatherName}</p>
<p><span className="attribute-name">...{studies}</p>
....
</AnimateHeight>
</div>
<div className="allumni-actions" onClick={this.updateHeight}>
<AccountCardDetailsOutlineIcon size={30}/>
<span className={'actions-toggle ' + (this.state.height >= 0 ? '' : 'rotated') }><ChevronDownIcon/></span>
</div>
</div>
);
}
}
export default Allumni
thank you
Related
In the TableAndInfo Component, each row's parent element is rendered by the renderEls function and the content is returned by the renderSection function. This passes in the content in the sections property as a parameter, as well as the class of the parent container. Passing the props value of date, time, and current in the sections array results in a successful initial render, but they do not continue updating when the state changes. I have inspected the code with the React Developer Tools and I see that the state is being updated by the functions defined in the App function and other components. How do I ensure that the grandchildren elements are re-rendered when the state is updated? Sorry if this doesn't make sense, I was having trouble trying to explain the problem. Thanks in advance!
function App() {
var [component, updateView] = useState('ServerFunctions');
var updateDateAndTime = function(formatDate) {
setInterval(function() {
if (document.getElementsByClassName('date')[0] && document.getElementsByClassName('time')[0]) {
updateDate(formatDate('date'));
updateTime(formatDate('time'));
}
}, 1000);
};
useEffect(() => {
updateDateAndTime(formatDate);
});
var [date, updateDate] = useState(formatDate('date'));
var [time, updateTime] = useState(formatDate('time'));
switch(component) {
case 'Welcome':
return (<Welcome updateView={updateView} date={date} time={time} backspacePinpad={backspacePinpad} />);
case 'ServerFunctions':
return (<ServerFunctions updateView={updateView} date={date} time={time} />);
default:
return null;
}
}
class TablesAndInfo extends React.Component {
sections = [[['info-row pct-space-b', 'flex between full-h'], {
1: ['Menu', 'button', 'gray white-f clickable roundish quarter-w label clickable'],
2: [this.props.current, 'div', 'f-override white-b small left three-quarter-w no-txt-overflow'],
}], [['info-table full-h', 'flex full-h'], {
1: ['Another Round', 'button', 'info-button blue white-f clickable'],
2: ['Select All', 'button', 'info-button gray white-f clickable'],
3: ['Name Check', 'button', 'info-button yellow clickable'],
}], [['tables-section full-h', 'tables-section full-h white-b'], {
}], [['new-table-b full-h', 'new-table-b med single round label full-h full-w'], {
1: ['New Table', 'button', 'blue white-f med single clickable round label clickable full-h full-w']
}]];
renderEls(num, classes) {
return (
<div className={classes}>
{this.sections[num].map((child, key) => {
if (typeof child === 'object' && !(child instanceof Array)) {
return (
<div className={this.sections[num][0][0]}>{this.renderSection(child, this.sections[num][0][1], key)}</div>
)
} else {
return null;
}
})}
</div>
)
}
renderSection(obj, parentClass) {
return (
<div className={parentClass}>
{Object.keys(obj).map((key, i) => {
if (obj[key][1] === 'button') {
return (
<button key={i} className={"flex center " + obj[key][2]}>{obj[key][0]}</button>
)
} else {
return (
<div key={i} className={"flex center " + obj[key][2]}>{obj[key][0]}</div>
)
}
})}
</div>
)
}
render() {
return (
<div className="tables space-r">
<div className="info tables-section">
{this.renderEls(0, 'info-table info-r')}
{this.renderEls(1, 'info-table info-b')}
</div>
{this.renderEls(2, 'table-view tables-section full-h pct-space-b')}
{this.renderEls(3, 'new-table')}
</div>
)
}
class ServerFunctions extends React.Component {
return (
<div className="App ServerFunctions">
<Header signOff={this.signOff} renderBttnRow={this.renderBttnRow} />
<div className="container order-control flex space-b">
<SelectedItems />
<TablesAndInfo current={this.state.current} date={this.props.date} time={this.props.time} />
<Functions current={this.state.current} />
</div>
<Footer current={this.state.current} renderBttnRow={this.renderBttnRow} />
</div>
)
}
}
Ended up solving this! Here's what I added in the TablesAndInfo Component:
constructor(props) {
super(props);
this.state = {current: this.props.current, date: this.props.date, time: this.props.time}
}
shouldComponentUpdate(nextProps) {
if ((nextProps.current !== this.state.current) || (nextProps.date !== this.state.date) || (nextProps.time !== this.state.time)) {
this.setState({current: nextProps.current});
this.setState({date: nextProps.date});
this.setState({time: nextProps.time});
}
return true;
}
componentDidUpdate() {
this.sections[0][1][2][0] = this.state.current;
this.sections[0][2][1][0] = this.state.time;
this.sections[0][2][2][0] = this.state.date;
}
Why does ReactJS remove the last element when the array is different after removing the middle element when using array.splice?
This is my code. I am using React-Redux.
const reducerNotesAndLogin = (state = initialState, action) => {
var tableNotes = "notities";
var tableCategories = "categories";
switch(action.type){
case "CATEGORY_REMOVE":
// Remove the category
var newCategories = state.categories;
console.log("state.categories", state.categories);
console.log("before: ", {newCategories});
var index = 0;
for(var i = 0; i < newCategories.length; i++){
if(newCategories[i].id === action.payload.categoryId){
newCategories.splice(i, 1);
index = i;
i--;
}
}
console.log("after: ", {newCategories});
state = {
...state,
categories: newCategories
}
break;
default:
break;
}
return state;
}
export default reducerNotesAndLogin;
Output below (I deleted the middle element. My web app always removes the last element of the categories (but not from the array).
Step 1: Initial state
Step 2: Remove middle item, expecting the middle item to be removed.
Step 3: Confusion
Why is the array correct, but the view incorrect? I am updating the state.categories correctly right?
This is my render code (as is - without filtering away any other code that mihgt be important)
CategoriesBody:
import React from 'react';
import { connect } from 'react-redux';
import CategoryItem from './CategoryItem';
import Button from './../../Button';
import store from '../../../redux/store-index';
class CategoriesBody extends React.Component {
render(){
return (
<div>
<ul className="list--notes">
{this.props.categories.map((category) => {
if(category.id === undefined){ // No categories
return <li>No categories</li>
} else {
return (
<div>
<CategoryItem category={category} />
<div className="mb-small hidden-sm hidden-md hidden-lg"> </div>
</div>
);
}
})}
</ul>
</div>
);
}
}
function mapStateToProps(state){
return {
categories: state.reducerNotesAndLogin.categories,
categoriesLength: state.reducerNotesAndLogin.categories.length
};
}
export default connect(mapStateToProps)(CategoriesBody);
CategoriesItem.js:
import React from 'react';
import store from './../../../redux/store-index';
import Button from './../../Button';
class CategoryItem extends React.Component {
constructor(props){
super();
this.state = {
edit: false,
categoryName: props.category.categoryName,
categoryColor: props.category.categoryColor
}
this.onClickEdit = this.onClickEdit.bind(this);
this.onChangeCategoryColor = this.onChangeCategoryColor.bind(this);
this.onChangeInputCategoryName = this.onChangeInputCategoryName.bind(this);
this.onClickEditSave = this.onClickEditSave.bind(this);
this.onClickEditCancel = this.onClickEditCancel.bind(this);
}
removeCategory(id, name){
console.log("nsvbsvbfjvbdjhbvv");
store.dispatch({ type: "CATEGORY_REMOVE", payload: {
categoryId: id
}});
// store.dispatch({type: "NOTIFY", payload: {
// type: 'success',
// message: 'Category "' + name + '" removed!'
// }});
}
onClickEdit(){
this.setState({
edit: true
});
}
onChangeCategoryColor(e){
this.setState({
categoryColor: e.target.value
});
}
onChangeInputCategoryName(e){
this.setState({
categoryName: e.target.value
});
}
onClickEditSave(){
this.setState({
edit: false,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
});
store.dispatch({type: "CATEGORY_EDIT", payload: {
categoryId: this.props.category.id,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
}});
store.dispatch({type: "NOTIFY", payload: {
type: "success",
message: "Category saved!"
}});
}
onClickEditCancel(){
this.setState({
edit: false,
categoryName: this.props.category.categoryName,
categoryColor: this.props.category.categoryColor
});
}
render(){
return (
<li key={this.props.category.id} className={this.state.edit === true ? "mt mb" : "flex-justify-between flex-align-center"}>
<div className={this.state.edit === true ? "d-none" : ""}>
<div className="input--color" style={{
backgroundColor: this.state.categoryColor
}}> </div>
{this.state.categoryName}
</div>
{/* Mobile */}
<div className={this.state.edit === true ? "d-none" : "hidden-sm hidden-md hidden-lg"}>
<Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button>
<div className="mt-small"> </div>
<Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button>
</div>
{/* Tablet and desktop */}
<div className={this.state.edit === true ? "d-none" : "hidden-xs"}>
<div style={{float:'left',}}><Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button></div>
<div style={{float:'left',marginLeft:'15px'}}><Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button></div>
</div>
{/* EDITING STATE */}
<div className={this.state.edit === true ? "" : "d-none"}>
<div className="row">
<div className="col-xs-12">
<input onChange={this.onChangeCategoryColor} className="input--wide" type="color" value={this.state.categoryColor}
style={{backgroundColor: this.state.categoryColor, height: '30px'}}
/>
<input onChange={this.onChangeInputCategoryName} className="input--wide" type="text" value={this.state.categoryName} />
</div>
</div>
<div className="row mt">
<div className="col-xs-12">
<Button buttonType="primary" onClick={() => this.onClickEditSave()}>Save</Button>
</div>
</div>
<div className="row mt-small">
<div className="col-xs-12">
<Button buttonType="secondary" onClick={() => this.onClickEditCancel()}>Cancel</Button>
</div>
</div>
</div>
</li>
)
}
}
export default CategoryItem;
I think it has something to do with the rendering. Because the arrays are correct when I console.log them. Only the view is different...
Do not modify the state in reducer directly. Create a copy of state value and then modify it.
Change:
var newCategories = state.categories;
To:
var newCategories = [...state.categories];
You should not modify the same array while looping through it.
for (var i = 0; i < newCategories.length; i++) {
if (newCategories[i].id === action.payload.categoryId) {
newCategories.splice(i, 1);
index = i;
i--;
}
}
I got the answer after looking through it with a friend of mine. The solution is pretty simple...
Lesson 101: Make sure that you have a unique "key" property when looping through an array in your UI.
The solution is to add this to my code:
<div key={category.id}>
{this.props.categories.map....
...
</div>
class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: false};
}
showOffers=( )=>{
this.setState({showoffer: !this.state.showoffer});
}
render() {
return (
<div className="OSServicesContainer">
<img className="OSlogomark" src={logomark} alt="logo mark" />
<article className="OssHeadingText">OOM INTERIORS OFFERS</article>
{offersdata.map((offers,index)=>{
return ( <div key={index} className="OssoffersContainermain">
<div className="OssoffersContainer">
<div className="OssofferHeadingmain">
<article className="OssofferHeading">{offers.heading}</article>
</div>
<article className="OssofferText">{offers.subheading}</article>
<div className="OssofferViewbtnmain">
<article key={index} className="OssofferViewbtn" onClick={this.showOffers}>{this.state.showoffer?"View Less":"View More"}</article>
</div>
</div>
{!this.state.showoffer?
null:
<div className="OssOfferSubCompmain">
{offers.offersub.map((offer,key) =>{
return <OssOfferSubComp ofrtext={offer.text} ofrsubtext={offer.subtext} />
})}
</div>}
</div>
)
})}
</div>);
}
}
export default Services;
Above is my code
i want to call showoffer function and update only that element clicked
please what shall i do it is triggering all elements
how to trigger single element??
You can try something like this:
`class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: 0};
}
showOffers = ( offerIndex ) => {
this.setState({showoffer: offerIndex});
}
hideOffers = () => {
this.setState({showoffer: 0});
}
render() => {
...
<div className="OssofferViewbtnmain">
<article key={index} onClick={ () => this.showOffers(index) }>
{this.state.showoffer?"View Less":"View More"}
</article>
</div>
...
{
this.state.showOffer && this.state.showOffer === index
? // then show it
: ''
}
}`
Hey if you wish to have multiple items open at the same time you can do something like this where you mutate the mapped item to track show hide state. I have added a visible property to the list item that keeps track if the item is open or closed:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends Component {
state = {
items: [
{ header: "Test 1", extra: "Some extra content A" },
{ header: "Test 2", extra: "Some extra content B" },
{ header: "Test 3", extra: "Some extra content C" }
]
};
onItemClick(index) {
const selected = this.state.items[index];
this.setState({
items: [
...this.state.items.slice(0, index),
{ ...selected, visible: !selected.visible },
...this.state.items.slice(index + 1)
]
});
}
render() {
return (
<div>
<ul>
{this.state.items.map((item, index) => {
return (
<li
key={index}
style={{ cursor: "pointer" }}
onClick={() => this.onItemClick(index)}
>
<h3>{item.header}</h3>
{item.visible ? <div>{item.extra}</div> : null}
</li>
);
})}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
https://codesandbox.io/s/busy-germain-hdmrn
I have a class component(actually the collection of the same components) where I have 2 buttons + and - to increase and decrease quantity of watches. Min amount of watches is 1 and max amount is 10. I have regulated this with this 2 functions increaseAmountHandler and decreaseAmountHandler. With this two buttons it's all ok. But the problem is that I have to sum up the value of calculated watches in parent component and I cannot forward the summed up values of the watches to a parent component to state variable totalAmount. Cannot use Redux becacuse it's a collection of watches component and each have own + and - button already occupied with this 2 increaseAmountHandler, decreaseAmountHandler functions.
Anyone idea how to solve this?
Child component:
import React, { Component } from 'react';
import Modal from '.././UI/Modal';
class SelectedWatch extends Component {
constructor(props) {
super(props)
this.state = {
watchQuantity: 1,
watchAmount: 1
}
}
increaseAmountHandler = () => {
if(this.state.watchQuantity < 1) {
this.setState({
watchQuantity: 0,
watchAmount: 0
})
return;
} else if (this.state.watchQuantity >= 10){
this.setState({
watchQuantity: 10,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
return;
}
this.setState({
watchQuantity: this.state.watchQuantity + 1,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
}
decreaseAmountHandler = () => {
if(this.state.watchQuantity < 1) {
this.setState({
watchQuantity: 0,
watchAmount: 0
})
return;
} else if (this.state.watchQuantity >= 10){
this.setState({
watchQuantity: 9,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
return;
}
this.setState({
watchQuantity: this.state.watchQuantity - 1,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
}
render() {
return (
<div className={"shopping-cart-product" + (this.state.watchQuantity < 1 ? ' notDisplayed' : '')}>
<div className="product-info">
<div>
<h3>{this.props.selectedWatch.selectedWatchName}</h3>
<p>${this.props.selectedWatch.selectedWatchPrice} × {this.state.watchQuantity}</p>
</div>
<img src={this.props.selectedWatch.selectedWatchUrl} />
</div>
<div className="product-count">
<button onClick={this.decreaseAmountHandler}>-</button>
<span>{this.state.watchQuantity}</span>
<button onClick={this.increaseAmountHandler}>+</button>
</div>
</div>
);
}
}
export default SelectedWatch;
Parent component:
import React, { Component } from 'react';
import EnteredWatch from '.././components/EnteredWatch/EnteredWatch';
import SelectedWatch from '.././components/SelectedWatch/SelectedWatch';
class App extends Component {
constructor(props) {
super(props)
this.state = {
watchName: '',
watchDescription: '',
watchUrl: '',
watchPrice: '',
watchId: '',
watchAmount: '',
watchQuantity: 1,
enteredWatchList: [],
selectedWatchName: '',
selectedWatchDescription: '',
selectedWatchUrl: '',
selectedWatchPrice: '',
selectedWatchId: '',
selectedWatchAmount: '',
selectedWatchQuantity: 1,
selectedWatchList: [],
totalAmount: 0,
}
}
submitHandler = (event) => {
event.preventDefault();
let watchId = Math.floor((Math.random() * 100) + 1);
let watchName = this.state.watchName;
let watchDescription = this.state.watchDescription;
let watchUrl = this.state.watchUrl;
let watchPrice = this.state.watchPrice;
let watchQuantity = this.state.watchQuantity;
this.setState({
enteredWatchList: this.state.enteredWatchList.concat({watchName, watchUrl, watchDescription, watchPrice, watchId, watchQuantity})
})
add = (selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity) => {
let arr = this.state.selectedWatchList;
let found = arr.some(el => {
return el.selectedWatchName === selectedWatchName;
});
if (!found) {
return arr.concat({selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity});
} else {
return this.state.selectedWatchList;
}
}
buyWatchHandler = (selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity) => {
let arr = this.add(selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity);
this.setState({
selectedWatchName: selectedWatchName,
selectedWatchUrl: selectedWatchUrl,
selectedWatchDescription: selectedWatchDescription,
selectedWatchPrice: selectedWatchPrice,
selectedWatchId: index,
selectedWatchQuantity: selectedWatchQuantity,
selectedWatchList: arr
});
}
render() {
const enteredWatches = this.state.enteredWatchList.map((enteredWatch, index) => {
return <EnteredWatch
key={index}
enteredWatch={enteredWatch}
selected={this.buyWatchHandler.bind(this, enteredWatch.watchName, enteredWatch.watchUrl,
enteredWatch.watchDescription, enteredWatch.watchPrice, index, enteredWatch.watchQuantity)}
/>
});
const selectedWatches = this.state.selectedWatchList.map((selectedWatch, index) => {
const active = this.state.activeIndex;
return <SelectedWatch
key={index}
active={index === active}
selectedWatch={selectedWatch}
/>
});
return (
<div className="App">
<div className="container-fluid">
<div className="container">
<div className="add-product">
<form>
<div>
<label>Product name:</label>
<input
type="text"
placeholder="Casio Watch"
required
value={this.state.watchName}
onChange={event => this.setState({watchName: event.target.value})}
/>
</div>
<div>
<label>Product description:</label>
<textarea
placeholder="Sample description..."
value={this.state.watchDescription}
onChange={event => this.setState({watchDescription: event.target.value})}
>
</textarea>
</div>
<div>
<label>Product image:</label>
<input
type="text"
placeholder="http://...jpg"
value={this.state.watchUrl}
pattern="https?://.+" required
onChange={event => this.setState({watchUrl: event.target.value})}
/>
</div>
<div>
<label>Product price:</label>
<input
type="number"
min="0"
placeholder="22"
value={this.state.watchPrice}
onChange={event => this.setState({watchPrice: event.target.value})}
/>
</div>
<button
type="submit"
onClick={event => this.submitHandler(event)}
>
Add a new Task
</button>
</form>
</div>
<div className="list-products">
<ul>
{enteredWatches}
</ul>
</div>
<div className="shopping-cart">
<div className="shopping-cart-products">
<ul>
{selectedWatches}
</ul>
</div>
<div className="shopping-cart-summary">
<div>Total: <b>${this.state.totalAmount}</b></div>
<div><button onClick={this.summaryHandler}>Purchase</button></div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default App;
The parent has to keep track of how many watches have been added.
Make the parent smart (has state), and the children dumb (no state).
Manage all the state in the parent, and put the click handlers in the parent too.
Pass those handlers down to the child, to be fired when its buttons are clicked. Something like this:
class Parent extends React.Component {
this.state = {
cart: [],
watches: [
{ id: 1, name: "Casio", description: "...", price: 25 },
{ id: 2, name: "Rolex", description: "...", price: 3000 },
{ id: 3, name: "Timex", description: "...", price: 10 },
],
}
handleClickIncrement = (watchId) => {
//add it to the cart (or increment it if its already there)
}
handleClickDecrement = (watchId) => {
//remove it from the cart (or deccrement it if its already there)
}
getCartTotal() {
//loop over cart and calculate
}
renderWatches() {
this.state.watches.map(watch => (
<Watch id={watch.id}
name={watch.name}
description={watch.description}
price={watch.price}
onClickIncrement={() => { this.handleClickIncrement(watch.id); }}
onClickDecrement={() => { this.handleClickDecrement(watch.id); }}
))
}
render() {
<div>
<h1>Our selection of watches:</h1>
{this.renderWatches()}
<h1>Your cart total: {this.getCartTotal()}</h1>
</div>
}
}
class Watch extends React.Component {
props = {
id,
name,
description,
price,
quantityInCart,
onClickIncrementButton,
onClickDecrementButton
}
render() {
<div>
<h1>{this.props.name}</h1>
<p>{this.props.description}</p>
<h5>${this.props.price}</h5>
<button onClick={this.props.onClickIncrementButton}>+</button>
<button onClick={this.props.onClickDecrementButton}>-</button>
</div>
}
}
I have a parent component that holds a number of children components. I want to add an active className to a a child component when it is clicked on.
This is working, but the issue is that each child component can have an active classname. Only one of the components should have be active each time.
Does anyone have any idea how to solve this issue?
Please see my code below.
class MyComponent extends Component {
constructor(props) {
super(props);
this.addActiveClass= this.addActiveClass.bind(this);
this.state = {
active: false,
};
}
addActiveClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
};
render() {
return (
<div
className={this.state.active ? 'active': null}
onclick={this.addActiveClass}
>
<p>{this.props.text}</p>
</div>
)
}
}
class Test extends Component {
render() {
return (
<div>
<MyComponent text={'1'} />
<MyComponent text={'2'} />
<MyComponent text={'3'} />
<MyComponent text={'4'} />
</div>
);
}
}
Add the active functionality to your Test Component.
There you can check whether there is already active Component or not.
class MyComponent extends React.Component {
render() {
return <div
id={this.props.id}
className={this.props.active ? 'active': null}
onClick={this.props.handleClick} >
{this.props.text}
</div>
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
components: [
{id: 1, text: 1},
{id: 2, text: 2},
{id: 3, text: 3},
{id: 4, text: 4}
],
activeID: null
};
}
handleClick(e) {
// If there is already active component ID, don't set another one!
// We support only one active ID.
if (this.state.activeID !== null) return;
const id = parseInt(e.target.id);
this.setState({
activeID: id
});
}
renderComponents() {
return (this.state.components.map( c =>
<MyComponent
id={c.id}
active={c.id === this.state.activeID}
text={c.text}
handleClick={this.handleClick.bind(this)} /> ));
}
renderActiveIDText() {
return (this.state.activeID ? <p>{"Active Component ID: " + this.state.activeID}</p> : null );
}
render() {
return <div>
{this.renderActiveIDText()}
{this.renderComponents()}
</div>
}
}
ReactDOM.render(<Test />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>