set state of specific attribute inside of an object - reactjs

I'm struggling to figure out the syntax for setting the state of an object inside of an array. I'm trying to access the fruits amount attribute. I'm familiar with concat for adding a new object and such but instead of adding a new updated object, how do I replace the value of an attribute inside of an object keeping everything the same except the attribute that changed and not adding a completely new object.
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
import Fruits from'./Fruits';
class App extends Component {
constructor(props) {
super(props);
this.state = {
fruits: [
{
type:'apple',
amount:10,
color:'green',
id: 0
},
{
type:'tomato',
amount:'25',
color:'red',
id: 1
}
]
};
}
renderFruits = () => {
const { fruits } = this.state;
return fruits.map((item, index) =>
<Fruits
key={index}
type={item.type}
amount={item.amount}
color={item.color}
id={item.id}
increment={this.increment}
/>
);
}
increment = (fruitId) => {
const { fruits } = this.state;
const incrementedFruit = fruits.filter((item) => {
return item.id == fruitId;
})
//{fruits: {...fruits, [fruits.amount]: [fruits.amount++]}}
}
render() {
return (
<div>
{this.renderFruits()}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Fruits Component
import React, { Component } from 'react';
class Fruits extends Component {
increment = () => {
const { id } = this.props;
this.props.increment(id);
}
decrement = () => {
console.log("decremented");
}
render() {
const { type, id, amount, color} = this.props;
return(
<div>
<span>
{type}
<ul>
<li>amount: {amount} </li>
<li>color: {color} </li>
</ul>
<button onClick={this.increment}> + </button>
<button onClick={this.decrement}> - </button>
</span>
</div>
);
}
}
export default Fruits;
stackblitz project

First pass index from fruit, lets take as index prop :
<Fruits
key={item.id}
index = {index}
type={item.type}
amount={item.amount}
color={item.color}
id={item.id}
increment={this.increment}
/>
Then pass index, instead of id of fruit on increment :
increment = () => {
this.props.increment(this.props.index);
}
You can make your increment function 2 ways :
1 : with state mutation , Please read : State Mutation
increment = (index) => {
++this.state.fruits[index].amount;
this.forceUpdate();
//OR
this.setState({fruits : this.state.fruits});
}
2 : without state mutation
increment = (index) => {
let fruits = this.state.fruits.slice();
++fruits[index].amount;
this.setState({fruits});
}
P.S: Also found that you are using the array index as key. This is
deprecated. See
https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
So also change :
from key={index} to key={item.id}

just change your increment function to this:
increment = (fruitId) => {
const { fruits } = this.state;
fruits.forEach((fruit)=>{
if(fruit.id == fruitId){
fruit.amount = parseInt(fruit.amount)+ 1;
}
})
this.setState({fruits: fruits})
}
Edited

Related

how to toggle a boolean inside an array

import React, { useState } from "react";
import { v4 as uuidv4 } from 'uuid';
const ReturantInfo = ()=>{
const data = [
{
name: "Haaland Restaurant's",
stars: 5,
price:"100$",
ranking:"top 1 in england",
ids:[
{id:uuidv4(),key:uuidv4(),visibility:false},
{id:uuidv4(),key:uuidv4(),visibility:false},
{id:uuidv4(),key:uuidv4(),visibility:false},
]
}
]
const [restaurantData,setRestaurantData] = useState(data)
const CardElement = restaurantData.map((data)=>{
return(
<div style={{color:"white"}}>
<h1>{data.name}</h1>
<div>
<div>
<h1>Stars</h1>
<p
id={data.ids[0].id}
onClick={()=>toggleVisibility(data.ids[0].id)}
>show</p>
</div>
{ data.ids[0].visibility ? <p>{data.stars}</p> : ""}
</div>
<div>
<div>
<h1>Price</h1>
<p
id={data.ids[1].id}
onClick={()=>toggleVisibility(data.ids[1].id)}
>show</p>
</div>
{ data.ids[1].visibility ? <p>{data.price}</p> : ""}
</div>
<div>
<div>
<h1>Ranking</h1>
<p
id={data.ids[2].id}
onClick={()=>toggleVisibility(data.ids[2].id)}
>show</p>
</div>
{ data.ids[2].visibility ? <p>{data.ranking}</p> : ""}
</div>
</div>
)
})
function toggleVisibility(id) {
setRestaurantData((prevData) =>
prevData.map((data) => {
data.ids.map(h=>{
return h.id === id ? {...data,ids:[{...h,visibility:!h.visibility}]} : data
})
})
);
}
return(
<div>
{CardElement}
</div>
)
}
export default ReturantInfo
that's a small example from my project I want to toggle visibility property by depending on the id of the clicked element and then if it equals to the id in the array then change the visibility to the opposite.
It looks like your handler isn't mapping the data to the same shape as what was there previously. Your data structure is quite complex which doesn't make the job easy, but you could use an approach more like this, where you pass in the element of data that needs to be modified, as well as the index of hte ids array that you need to modify, then return a callback that you can use as the onClick handler:
function toggleVisibility(item, index) {
return () => {
setRestaurantData((prevData) => prevData.map((prevItem) => {
if (prevItem !== item) return prevItem;
return {
...prevItem,
ids: prevItem.ids.map((id, i) =>
i !== index ? id : { ...id, visibility: !id.visibility }
),
};
}));
};
}
And you'd use it like this:
<p onClick={toggleVisibility(data, 0)}>show</p>
While you're there, you could refactor out each restaurant "property" into a reusable component, since they're all effectively doing the same thing. Here's a StackBlitz showing it all working.
function toggleVisibility(id) {
setRestaurantData(
restaurantData.map((d) => {
const ids = d.ids.map((h) => {
if (h.id === id) {
h.visibility = !h.visibility;
}
return h;
});
return { ...d, ids };
})
);
}

Could'nt return maped item inside jsx component

Hey Guys I am trying to display a list of items according to categories,
this is my json structure. I want to display objects according to itemCategory. for e.g if there are two or more pizza they should come under one category heading.
{
"itemid": 3,
"itemName": "Veg OverLoaded",
"itemPrice": 300.0,
"itemDescription": "chessy, tasty, covered with fresh veggies",
"itemCategory": "pizza"
},
for this i created a map of objects and passed the data according to category as key.
import React, { forwardRef,useState } from 'react';
import MenuItem from './MenuItem';
import './styles.css';
import Category from '../../Home/Category'
const NewMap = new Map()
const Menu = forwardRef(({ list }, ref) => (
<>
<main ref={ref} >
{Object.values(list).map((k) => {
if (NewMap.has(k.itemCategory)){
const itemList = NewMap.get(k.itemCategory);
const newItemList = [...itemList, k];
NewMap.set(k.itemCategory, newItemList);
}else{
NewMap.set(k.itemCategory , [k]);
}
})}
<MenuItem itemMap = {NewMap}/>
</main>
</>
));
i am passing the map to MenuItem as props and trying to display objects here
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import {
cartAddItem,
cartRemoveItem,
} from '../../../../redux/cart/cart.action';
import {
selectCartItems,
selectCartItemsCount,
} from '../../../../redux/cart/cart.selector';
import ButtonAddRemoveItem from '../../ButtonAddRemoveItem';
import './styles.css';
import Accordion from 'react-bootstrap/Accordion'
import useFetchData from './newData'
const MenuItem = ({
itemMap,
cartCount,
cartList,
cartAddItem,
cartRemoveItem,
}) => {
const {
data,
loading,
} = useFetchData();
const handleQuantity = () => {
let quantity = 0;
if (cartCount !== 0) {
const foundItemInCart = cartList.find((item) => item.itemid === 1);
if (foundItemInCart) {
quantity = foundItemInCart.quantity;
}
}
return quantity;
};
return (
<>
{itemMap.forEach((key, value) => {
{console.log(value)}
<div className='container-menu' >
{console.log(value)}
<ul>
{Object.values(key).map((blah) => {
<li>
<h1>{blah.itemName}</h1>
<div className='item-contents'>
{blah.itemName}
<p className='item-contents' style={{ float: "right", fontSize: "12" }}> ₹ {blah.itemPrice}</p>
<div>
<p className='description'>{blah.itemDescription}</p>
<ButtonAddRemoveItem
quantity={handleQuantity()}
handleRemoveItem={() => cartRemoveItem(blah)}
handleAddItem={() => cartAddItem(blah)}
/>
</div>
</div>
</li>
})}
</ul>
</div>
})}
</>
);
};
const mapStateToProps = createStructuredSelector({
cartCount: selectCartItemsCount,
cartList: selectCartItems,
});
const mapDispatchToProps = (dispatch) => ({
cartAddItem: (item) => dispatch(cartAddItem(item)),
cartRemoveItem: (item) => dispatch(cartRemoveItem(item)),
});
export default connect(mapStateToProps, mapDispatchToProps)(MenuItem);
export default Menu;
i am able to console.log itemName but i am unable to display it inside jsx component. Any reason why
? what am i missing here
The foreach loop should return JSX. In your case there is no return. I suggest removing the curly brackets.
WRONG:
itemMap.forEach((key, value) => {
<>
</>
})
CORRECT:
itemMap.forEach((key, value) => (
<>
</>
))
That's valid for all the loops inside your JSX.
To return an array of elements, you should use map instead of forEach like below code, because forEach loop returns undefined, while map always returns an array.
{itemMap.map((key, value) => {
return (<div className='container-menu'>
...
</div>)
}
}
or
{itemMap.map((key, value) => (<div className='container-menu'>
...
</div>)
}

if else statement not working in react component

I am trying to implement a condition in my react component . When the user triggers the onClick the state updates allStakes creating one array of 4 values. The problem is that I do not want the user to input more than 4 values so tried to give the limit by doing an if else statement. I tried to add a console.log in both statements.The weird fact is that setState get updated but the csonole.log is never displayed.The component keeps rendering all the values that I insert even if the array is longer than 4. Thanks in advance
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
allStakes: []
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
const { allStakes } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
onClick={(newStake) => {
if (allStakes.length <= 3) {
this.setState({ allStakes: [allStakes, ...newStake] })
console.log('stop')
} else if (allStakes.length == 4) {
console.log('more than 3')
}
}}
/>
</div>
</li>
))
}
</ul>
</div>
May be it happens because of incorrect array destructuring. Try to change this code:
this.setState({ allStakes: [allStakes, ...newStake] })
by the next one:
this.setState({ allStakes: [newStake, ...allStakes] })
Your state belongs to your FetchRandomBet component and you are trying to update that from your imported component. There are 2 solutions to that.
1> Wrap your Stake component to a separate component with onClick handler something like this
<div onClick={(newStake) => {
if (allStakes.length <= 3) {
this.setState({
allStakes: [allStakes, ...newStake
]
})
console.log('stop')
} else if (allStakes.length == 4) {
console.log('more than 3')
}
}}><Stake /></div>
Or
2> Pass the state as a prop to the Stake component which will be responsible to update the state for FetchRandomBet. something like this
<Stake parentState={this}/>
And inside the Stake component change the parentState on click of wherever you want.
I solved the problem. I transfered the onClick method in stake component and I handled the upload of the common array with an array useState. I add the value to newStake and when I click ok I retrieve newStake and spread it into a new array and then I check that array. If there is a value should not keep adding otherwise it can add values. It works fine. Thanks anyway
import React, { useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
function Stake(props) {
const [newStake, setStake] = useState(null);
const [allStakes, setStakes] = useState(null);
const changeStake = (e) => {
setStake([e.target.value])
}
const mySubmit = () => {
if (!allStakes) {
setStakes([...newStake, allStakes])
props.onClick(newStake);
} else if (allStakes) {
console.log('stop')
}
}
return (
<>
<CurrencyInput
onChange={changeStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
<button onClick={mySubmit}>yes</button>
<button>no</button>
{newStake}
</>
);
}
export default Stake;

Delete record from todo list in ReactJS giving error

Am learning ReactJS and building my todo application.
However am facing an issue when I try to delete a task.
I have two files TodoList.js and TodoItems.js
TodoList.js
import React, {Component} from 'react';
import TodoItems from './TodoItems';
class TodoList extends Component {
//Function to handle adding tasks
addTask(event) {
//Get task Value
let task = this.refs.name.value;
//Newitem Object
if (task !== "") {
let newItem = {
text: task,
key: Date.now()
}
this.setState({
items: this.state.items.concat(newItem)
});
this.refs.name.value = ""; //Blank out the task input box
}
}
deleteItem(key) {
var filteredItems = this.state.items.filter(function (item) {
return (item.key !== key);
});
this.setState({
items: filteredItems
});
}
constructor(props) {
super(props);
this.state = {
items: []
};
this.addTask = this.addTask.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form>
<input placeholder="Enter Task" id="name" ref="name"></input>
<button type="button" onClick={this.addTask}>Add Task</button>
</form>
</div>
<div className="list">
<TodoItems entries={this.state.items} delete={this.deleteItem} />
</div>
</div>
);
}
}
export default TodoList;
TodoItems.js has following code
import React, {Component} from 'react';
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {};
}
delete(key) {
this.props.delete(key);
}
listTasks(item) {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
render() {
let entries = this.props.entries;
let listItems = entries.map(this.listTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
}
export default TodoItems;
I am getting an error on deleting task when clicked on it.
and I am getting error as here
I guess it means function delete is not defined but it has been defined still am getting an error.
Can anyone explain how do I resolve this issue?
You should never attempt to modify your props directly, if something in your components affects how it is rendered, put it in your state :
this.state = {
entries: props.entries
};
To delete your element, just filter it out of your entries array :
delete(key) {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
And now the render function :
render() {
const { entries } = this.state //Takes the entries out of your state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
Full code :
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {
entries: props.entries
};
}
delete = key => ev => {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
render() {
const { entries } = this.state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
}
You should also try to never use var. If you do not plan to modify a variable, use const, otherwise, use let.
EDIT : The error shown in your edit come from listTasks not being bound to your class. To solve it you can either bind it (as shown in an other answer below) or convert it in another function :
listTasks = item => {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
Short syntax :
listTasks = ({ key, text }) => <li key={key} onClick={() => this.delete(key)}>{text}</li>
Welcome to Stackoverflow!
Check this section of the React Docs. You either have to bind your class functions in the constructor or use arrow functions.
class TodoItems extends Component {
constructor(props) {
// ...
this.delete = this.delete.bind(this);
}
delete(key) {
this.props.delete(key);
}
// Or without binding explicitly:
delete2 = (key) => {
// ...
}
}
Replace this:
onClick={this.delete(item.key)}
// passes the result of `this.delete(item.key)` as the callback
By this:
onClick={() => this.delete(item.key)}
// triggers `this.delete(item.key)` upon click

how to pass props down using map

I'm trying to understand how to pass props down using the map function. I pass down the fruit type in my renderFruits function and in my Fruits sub-component I render the fruit type. I do not understand what is wrong with this code.
import React, { Component } from 'react';
import { render } from 'react-dom';
import Fruits from'./Fruits';
class App extends Component {
constructor(props) {
super(props);
this.state = {
fruits: [
{
type:'apple',
},
{
type:'tomato',
}
]
};
}
renderFruits = () => {
const { fruits } = this.state;
return fruits.map(item =>
<Fruits
type={item.type}
/>
);
}
render() {
return (
<div>
{this.renderFruits}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Fruits component where it should render two divs with the text apple and tomato.
class Fruits extends Component {
render() {
const { type } = this.props;
return(
<div>
{type}
</div>
);
}
}
export default Fruits;
You have two problems in you code
- you should call renderFruits in your render function: this.renderFruits()
- should use "key", when you try to render array
renderFruits = () => {
const { fruits } = this.state;
return fruits.map( (item, index) =>
<Fruits
key={index}
type={item.type}
/>
);
}
render() {
return (
<div>
{this.renderFruits()}
</div>
);
}

Resources