i'm new on react and try to adding class based on array, but while i click in another button the active class should stay in the last button class when i click the other button, i don't have any clue to do so.
class Child extends React.Component {
render(){
return(
<button
onClick={this.props.onClick}
className={`initClass ${this.props.isClass}`}>
{this.props.text}
</button>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
newClass: null,
};
}
myArray(){
return [
"Button 1",
"Button 2",
"Button 3"
];
}
handleClick (myIndex,e) {
this.setState({
newClass: myIndex,
});
}
render () {
return (
<div>
{this.myArray().map((obj, index) => {
const ifClass = this.state.newClass === index ? 'active' : '';
return <Child
text={obj}
isClass={ifClass}
key={index}
onClick={(e) => this.handleClick(index,e)} />
})}
</div>
)
}
}
ReactDOM.render(<Parent/>, document.querySelector('.content'));
.active {
background: cyan;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div class='content'/>
Make your newClass an array and while putting the class check if that index exists in your state array.
constructor(props) {
super(props);
this.state = {
newClass: [], //an array
};
}
....
handleClick (myIndex,e) {
if(!this.state.newClass.includes(myIndex)){
this.setState({
newClass: [...this.state.newClass, myIndex],
});
}
}
....
render () {
const that = this;
return (
<div>
{this.myArray().map((obj, index) => {
const ifClass = that.state.newClass.includes(index) ? 'active' : '';
return <Child
text={obj}
isClass={ifClass}
key={index}
onClick={(e) => that.handleClick(index,e)} />
})}
</div>
)
}
....
As you haven't told when you need to remove the class, so not adding the step where you should extract some index from the array.
Related
My code below shows my current component design. This is a counter component which is responsible for incrementing a counter for the respective array item and also for adding the clicked item to the cart. I am trying to figure out if there is some way in which I can assign each array item within the items array to its own state count value. Currently, the screen shows four array items, with each one having a button next to it and also a count. When clicking the increment button for any particular item, the state count for all buttons is updated and rendered, which is not what I want. I have tried to assign each button it's own state count in several ways, but haven't been able to figure out the right way. I would like to somehow bind a state count value to each button so that each one has it's individual state count.I would really appreciate if someone can provide some tips or insight as I dont know of a way to isolate the state count for each button and make it unique so that when one value's button is clicked, only the state count for that particular button (located next to the increment button) is updated and not the others.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
cart: [],
};
}
handleIncrement = (e) => {
this.setState({
count: this.state.count + 1,
cart: [...this.state.cart, e.target.value],
});
};
render() {
const listItems = this.props.items.map((item) => (
<li key={item.id}>
{item.value}
<button onClick={this.handleIncrement}>+</button>
{this.state.count}
</li>
));
return (
<div>
{listItems}
</div>
);
}
}
What I did here is I remove the constructor, update Counter component props, update the event on how to update your cart in Example component, adjusted the Counter component, for the Cart component, I added componentDidMount and shouldComponentUpdate make sure that the component will re-render only when props listArray is changing. Here's the code.
class Example extends React.Component {
state = {
cart: [],
items: [
{ id: 1, value: "L1" },
{ id: 2, value: "L2" },
{ id: 3, value: "L3" },
{ id: 4, value: "L4" }
]
}
render() {
const { cart } = this.state
return (
<div>
<h1>List</h1>
{ items.map(
({ id, ...rest }) => (
<Counter
key={ id }
{ ...rest }
cart={ cart }
onAddToCard={ this.handleAddCart }
/>
)
) }
</div>
)
}
handleAddCart = (item) => {
this.setState(({ items }) => ([ ...items, item ]))
}
}
class Counter extends React.Component {
state = {
count: 0
}
handleIncrement = () => {
this.setState(({ count }) => ({ count: count++ }))
}
render() {
const { count } = this.state
const { cart, value } = this.props
return (
<div>
{ value }
<span>
<button onClick={ this.handleIncrement }>+</button>
{ count }
</span>
<Cart listArray={ cart } />
</div>
)
}
}
class Cart extends React.Component {
state = {
cart: []
}
addTo = () => (
<div>List: </div>
)
componentDidMount() {
const { cart } = this.props
this.setState({ cart })
}
shouldComponentUpdate({ listArray }) {
return listArray.length !== this.state.cart.length
}
render() {
return (
<div>
<ListFunctions addClick={ this.addTo } />
</div>
)
}
}
const ListFunctions = ({ addClick }) => (
<div>
<button onClick={ addClick }>Add To List</button>
</div>
)
If you want to add to the list of items without rendering the button, you can add a custom property to mark that it is a custom addition:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, value: "L1" },
{ id: 2, value: "L2" },
{ id: 3, value: "L3" },
{ id: 4, value: "L4" },
]
}
}
addToItems = items => {
this.setState({
items,
});
}
render() {
var cartArray = [];
return (
<div>
<h1>List</h1>
{this.state.items.map((item) =>
<Counter
key={item.id}
value={item.value}
id={item.id}
custom={item.custom}
cart={cartArray}
addToItems={this.addToItems}
items={this.state.items}
/>
)}
</div>
);
}
}
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleIncrement = () => {
this.setState({
count: this.state.count + 1,
});
this.props.cart.push(this.props.value);
};
addTo = () => {
const { items } = this.props;
let lastId = items.length;
lastId++;
this.props.addToItems([
...items,
{
id: lastId,
value: `L${lastId}`,
custom: true,
}]);
};
render() {
return (
<div>
{this.props.value}
{
!this.props.custom &&
(
<span>
<button onClick={this.handleIncrement}>+ </button>
{this.state.count}
</span>
)
}
<Cart addTo={this.addTo} />
</div>
);
}
}
class Cart extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<ListFunctions
addClick={this.props.addTo}
/>
</div>
);
return null;
}
}
const ListFunctions = ({ addClick}) => (
<div>
<button onClick={addClick}>Add To List</button>
</div>
);
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<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>
<div id="react"></div>
I'm new to react and getting trouble to achieve something very simple.
I have 3 boxes with initial black bg-color,
I need that whenever the user click on one of the boxes, just the color of selected box will change to white while the other elements stay at initial color, if the first box changed color and then we click on the second box, so the first box return to initial color and the second turn to white.
This is what I have done so far:
import React from 'react'
import { CardContainer, Title } from './business-item.styles';
import './business-item.style.scss';
class BusinessItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isActive: false
};
this.changeColor = this.changeColor.bind(this);
}
changeColor() {
this.setState({ isActive: true });
}
render() {
const {isActive} = this.state;
return (
<CardContainer
className={isActive ? 'choosen' : 'not-choosen'}
onClick={this.changeColor}>
<Title>{this.props.title}</Title>
</CardContainer>
)
}
}
export default BusinessItem;
I'm trying to create this screens:
You want to lift up state. The buttons are not independent from each other; they need to be controlled all together by their parent component. Something like:
class Select extends React.Component {
constructor(props) {
super(props);
this.state = { selected: null };
}
render(){
return (
<div>
<Button
selected={this.state.selected === "Dog"}
onClick={() => this.setState({selected: "Dog"})}
>Dog</Button>
<Button
selected={this.state.selected === "Cat"}
onClick={() => this.setState({selected: "Cat"})}
>Cat</Button>
</div>
)
}
}
class Button extends React.Component {
render(){
const className = this.props.selected ? "selected" : "";
return (
<button
className={className}
onClick={this.props.onClick}
>{this.props.children}</button>
)
}
}
You could lift your state up to track active item clicked
const BusinessItemContainer = ({businessItems}) => {
const [activeIndex, setActiveIndex] = useState(null)
return <>
{
businessItems.map((title, index) => <BusinessItem key={item} index={index} title={title} onClick={setActiveIndex} activeIndex={activeIndex}/ >)
}
</>
}
Then in your component
const BusinessItem = ({index, activeIndex, title, onClick}) => {
return (
<CardContainer
className={activeIndex === index ? 'choosen' : 'not-choosen'}
onClick={()=> onClick(index)}>
<Title>{title}</Title>
</CardContainer>
)
}
I have a basic 'wrapper' component which contains child 'item' components
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
render() {
return (
<div>Items count- {this.state.items.length}
{this.state.items.map(function (item, i) {
<Item itemId={item.itemId} />
})}
</div>
);
}
}
class Item extends React.Component {
constructor(props) { super(props); }
render() {
return (
<div class="item">{this.props.itemId}</div>
);
}
}
Do I call setState({ "items":[{ "itemId": 22 }] }); to update items in UI?
Want to add/remove 'item' and get UI updated accordingly.
For updates, you want to do something like the following...
// Update item
this.setState({ "items":this.state.items.map(function(item) {
if (item.itemId !== 22) return item;
// update item here
// remember to return item
return item
})
});
// Remove item
this.setState({ "items":this.state.items.filter(item => {
return item.itemId !== 22
})
})
// Add item
this.setState({ "items": this.state.items.concat(newItem)
})
I suggest putting these into React class methods though.
import React from 'react';
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
this.addItem = this.addItem.bind(this)
this.removeItem = this.removeItem.bind(this)
this.updateItem = this.updateItem.bind(this)
}
addItem (item) {
this.setState({
items: this.state.items.concat(item)
})
}
updateItem(id, updatedItem) {
this.setState({
items: this.state.items.map(function (item) {
if (item.itemId !== id) return item;
return updatedItem;
})
})
}
removeItem(id) {
this.setState({
items: this.state.items.filter(function(item) {
return item.itemId !== id
})
})
}
render() {
return (
<div>Items count- {this.state.items.length}
{this.state.items.map(function (item, i) {
<Item itemId={item.itemId} />
})}
</div>
);
}
}
class Item extends React.Component {
constructor(props) { super(props); }
render() {
return (
<div class="item">{this.props.itemId}</div>
);
}
}
State is not mutable, so the code you've shown there will replace items with an array with one object. If you'd like to add/remove from the array, you'll first need to copy the array somehow , and replace with the new one. You should use the function argument of setState for that. Ex:
this.setState(function (currentState) {
return {items: currentState.concat({itemId: 22})}
});
This is how you add and remove to and from the state items array:
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
addItems = (id) => {
// copies all current items and adds a new one
this.setState({items: [...this.state.items, {itemId: id}]
})
}
removeItems = (id) => {
const newItemList = this.state.items.filter((item) => item.itemId !== id)
this.setState({items: newItemList
})
}
render() {
return (
<div>
<div>Items count - {this.state.items.length}
<button onClick={() => this.addItems(this.state.items.length + 1)}>Add Item</button>
</div>
{
this.state.items.map((item) => {
return (
<Item key={item.itemId} itemId={item.itemId} removeItems={this.removeItems} />
)
})
}
</div>
);
}
}
ReactDOM.render(<Wrapper />, document.getElementById('root'))
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div className="item">test{this.props.itemId} <button onClick={() => this.props.removeItems(this.props.itemId)}>Remove Item</button></div>;
}
}
<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>
<div id='root'></div>
i have a problem - i need to change parent component's state from child component. I tried standard variant with props, but it's not helping in my case.
Here is code of Parent component:
class ThemesBlock extends React.Component {
constructor(props){
super(props);
this.state = {
currentThemeId: 0
}
}
changeState(n){
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
data.map(function (item) {
return <Theme key={item.id} data={item} changeState=
{item.changeState}/>
})
)
}
}
And here is my code for Child Component:
class Theme extends React.Component {
constructor(props){
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const { id, themename } = this.props.data;
const link = '#themespeakers' + id;
return (
<li><a href={link} onClick={() => this.changeState(id)}
className="theme">{themename}</a></li>
)
}
}
The primary issue is that changeState should be bound to the ThemesBlock instance, not to the Theme instance (by ThemesBlock).
Here's an example where I've bound it in the ThemesBlock constructor (I've also updated it to show what theme ID is selected):
class ThemesBlock extends React.Component {
constructor(props) {
super(props);
this.state = {
currentThemeId: 0
}
this.changeState = this.changeState.bind(this);
}
changeState(n) {
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
<div>
<div>
Current theme ID: {this.state.currentThemeId}
</div>
{data.map(item => {
return <Theme key={item.id} data={item} changeState={this.changeState} />
})}
</div>
)
}
}
class Theme extends React.Component {
constructor(props) {
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const {
data: {
id,
themename
},
changeState
} = this.props;
const link = '#themespeakers' + id;
return (
<li>{themename}</li>
)
}
}
const data = [
{id: 1, themename: "One"},
{id: 2, themename: "Two"},
{id: 3, themename: "Three"}
];
ReactDOM.render(
<ThemesBlock data={data} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Did you try to move map() before return? Like this:
render() {
let { data } = this.props;
const outputTheme = data.map(function (item) {
return <Theme key={item.id} data={item} changeState= {item.changeState}/>
})
return (
{outputTheme}
)
}
I am a React novice, so this might seem really simple, or maybe it isn't, I'm not sure. I'm building a basic to-do list. I want a mouseover effect on list items to pop up "delete this" text. But for my code so far, when I mouseover a list item, "delete this" pops up for all list items instead of just the one.
When I tried solving this by creating a new component for individual list items, that didn't seem to work. Any help is much appreciated!
class ToDosContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
heading: 'Something You Need To Do?',
todos: [
'wake up',
'brush your teeth'
],
}
this.addToDo = this.addToDo.bind(this)
}
addToDo(todo) {
this.setState((state) => ({
todos: state.todos.concat([todo])
}))
}
render() {
return (
<div>
<h1>To Do List</h1>
<h3>{this.state.heading}</h3>
<AddToDo addNew={this.addToDo} />
<ShowList tasks={this.state.todos} />
</div>
)
}
}
class AddToDo extends React.Component {
constructor(props) {
super(props)
this.state = {
newToDo: ''
}
this.updateNewToDo = this.updateNewToDo.bind(this)
this.handleAddToDo = this.handleAddToDo.bind(this)
}
//I think I can delete this part
updateNewToDo(e) {
this.setState({
newToDo: e.target.value
})
}
//
handleAddToDo() {
this.props.addNew(this.state.newToDo)
this.setState({
newToDo: ''
})
}
render() {
return (
<div>
<input
type="text"
value={this.state.newToDo}
onChange={this.updateNewToDo}
/>
<button onClick={this.handleAddToDo}> Add To Do </button>
</div>
)
}
}
class ShowList extends React.Component {
constructor(props) {
super(props)
this.state = {
newToDo: ''
}
}
onMouseOver(e) {
this.setState({
text: 'delete me'
})
console.log('hey')
}
onMouseOut(e) {
this.setState({
text: ''
})
console.log('hey hey')
}
render() {
const { text } = this.state;
return (
<div>
<h4>To Do's</h4>
<ul>
{this.props.tasks.map((todo) => {
return <li onMouseEnter={this.onMouseOver.bind(this)} onMouseLeave={this.onMouseOut.bind(this)}> {todo} {text}</li>
})}
</ul>
</div>
)
}
}
ReactDOM.render(<ToDosContainer />, document.getElementById('helloworld'));
I would make a Task component. You don't want to set the state of the text in the ListView component, because then this.state.text is shared by each task in the map. Each task should be aware of its own hover, and not the hover of the other children.
class Task extends React.Component {
state = {
text: ""
};
onMouseOver(e) {
this.setState({
text: "delete me"
});
}
onMouseOut(e) {
this.setState({
text: ""
});
}
render() {
return (
<li
onMouseEnter={this.onMouseOver.bind(this)}
onMouseLeave={this.onMouseOut.bind(this)}
>
{this.props.todo} {this.state.text}
</li>
);
}
}
class ShowList extends React.Component {
constructor(props) {
super(props);
this.state = {
newToDo: ""
};
}
render() {
const { text } = this.state;
return (
<div>
<h4>To Do's</h4>
<ul>
{this.props.tasks.map(todo => {
return <Task todo={todo} />;
})}
</ul>
</div>
);
}
}