how to setState and update an object within an array - javascript

i need your help please :-)
state = {
order: {
createdAt: Date.now(),
products: [
{
leftInStock: "12",
productName: "בלה",
productPrice: "120",
qtyInOrder: 0
}
],
totalPrice: 0,
miniClient: {
id: '',
fullName: ''
}
},
client: null,
isClientSelected: true
}
and this is the function that should preform the update on the object (need to update qtyInOrder).
setQtyForProduct = (product, name) => {
if (name === '+') product.qtyInOrder++
if (name === '-') {
if (product.qtyInOrder === 1) return
else product.qtyInOrder--
}
const { products } = this.state.order
this.setState({ order: { ...this.state.order, products: [...products, product] } }, () => { this.getOrderPrice() })
}
this is how i pass the values
<div className="order-selected-product-list">
{this.state.order.products.map((product, _id) => {
return (
<div key={_id} className="order-product">
<div className="order-selected-product-card">
<div>{product.productName}</div>
<div>{product.qtyInOrder}</div>
</div>
<div className="order-products-qty-btns">
<div onClick={() => this.setQtyForProduct(product, "+")}>+</div>
<div onClick={() => this.removeProductFromOrder(product)}>הסר</div>
<div onClick={() => this.setQtyForProduct(product, "-")}>-</div>
</div>
</div>
)
})}
</div>
i do get the values right , the only thing i caould not manage to do is update the
I know that is something wrong with my setState function cause it adds the products instead of updating the key in it

Related

State won't update

I'm working on order system for my portfolio and I need your help.
for some reason one of the state values won't update while other does.
state = {
currOrder: {
createdAt: Date.now(),
totalPrice: 0,
lastUpdated: Date.now(),
notes: '',
products: [],
miniClient: {
_id: '',
fullName: ''
}
},
client: this.props.client
}
componentDidMount() {
if (this.props.currOrder) {
const currOrder = this.props.currOrder
this.setState({ currOrder })
} else {
this.setState({ currOrder: { ...this.state.currOrder, miniClient: { _id: this.state.client._id, fullName: this.state.client.fullName } } })
}
}
onHandleChange = (ev) => {
if (ev.target) {
const { target } = ev
const field = target.name
const value = target.type === 'number' ? +target.value : target.value
this.setState((prevState) => ({ currOrder: { ...prevState.currOrder, [field]: value } }))
}
}
getOrderLastUpdate = () => {
const updateDate = Date.now()
this.setState({ currOrder: { ...this.state.currOrder, lastUpdated: updateDate } }, () => { console.log(this.state.currOrder) })
}
onSubmitOrder = () => {
const { currOrder } = this.state
if (!currOrder._id) {
this.props.addOrder(currOrder)
this.props.isEditModalOpen()
} else {
this.getOrderLastUpdate()
this.props.updateOrder(currOrder)
this.props.isEditModalOpen()
}
}
render() {
const { notes, totalPrice, lastUpdated } = this.state.currOrder
const { client } = this.props
return (
<div className="order-edit">
<div>{client.fullName}</div>
<div>{utilService.formattedDates(lastUpdated)} : עודכן לאחרונה</div>
<div className="order-edit-info-holder">
<input type="number"
name="totalPrice"
value={totalPrice}
onChange={this.onHandleChange} />
<label htmlFor="totalPrice">מחיר</label>
</div>
<div className="order-edit-info-holder">
<input type="text"
name="notes"
value={notes}
onChange={this.onHandleChange} />
<label htmlFor="totalPrice">הערות</label>
</div>
<div className="order-edit-btns-container">
<button onClick={this.onSubmitOrder}>שמור</button>
<button onClick={this.props.isEditModalOpen}>בטל</button>
</div>
</div>
)
}
for some reason it refuses to update the lastUpdated value (as all other values are working fine :-()
Am I doing something wrong?
as for now the CB for getOrderLastUpdate function (console.log) won't even work.

How can I change boolean value in objects?

I have such situation:
There is an array:
const arr = [
{
id: 0,
title: 'a',
status: false
},
{
id: 1,
title: 'a',
status: false
},
{
id: 2,
title: 'a',
status: false
},
]
Then I use this array in my React component to get values, but here I also need to change status to true or false in current element (not for all objects)
arr.map(el => {
return (
<div key={el.id}>
<div onClick={() => !status}>{div.title}</div> // here
</div>
)
})
So how can I make this?
How about simply (if you have the access to the arr variable) you have an option of getting an index of the array as the 2nd parameter in the map function.
arr.map((el, ix) => {
return (
<div key={el.id}>
<div onClick={() => {
arr[ix].status = !arr[ix].status
}}>{div.title}</div> // here
</div>
)
})
or pull it out in a function (better way) as
function handleClick (ix) {
const currentStatus = arr[ix].status
arr[ix].status = !currentStatus
}
arr.map((el, ix) => {
return (
<div key={el.id}>
<div onClick={() => handleClick(ix)}>{div.title}</div> // here
</div>
)
})
EDIT: Didn't see it was a reactJS, my bad, in that case the best case is to manage it with state.
You can try:
const handleClick = (el) => () => {
el.status = !el.status
}
arr.map(el => {
return (
<div key={el.id}>
<div onClick={handleClick(el)}>{div.title}</div> // here
</div>
)
})
Maintain array in react state;
const [arr,setArr]=useState([
{
id: 0,
title: 'a',
status: false
},
{
id: 1,
title: 'a',
status: false
},
{
id: 2,
title: 'a',
status: false
},
])
Write a function to handle the change in state
const changeStatus =id=>{
const newArr = arr.map((el,index)=>{
if(el.id===id){
return {...el,status:!el.status}
}
else return;
})
setArr(newArr)
}
call this function on onClick.
arr.map(el => {
return (
<div key={el.id}>
<div onClick={() => changeStatus(el.id)}>{div.title}</div> // here
</div>
)
})

Adding clicked items to new array in React

I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.
Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}
I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!

how to display group messages

Please help me, I'm new in react. I'm rendering values from nested object. Each object has title and message property. Titles can be same. I want display messages under title. If title same as in previos object do not display it , only once. But in my case it displays after each message.
my object:
arrayOfMessages=[
{
title: 'cars',
message: 'toyota'
},
{
title: 'cars',
message: 'ford'
},
{
title: 'cars',
message: 'bmw'
},
{
title: 'bikes',
message: 'suzuki'
},
{
title: 'bikes',
message: 'bmw'
},
]
expected output:
title
message
message
message
title2
message
message
in my case:
title
message
title
message
title
message
title2
message
title2
message
<div>
{arrayOfMessages.map((item, idx) => {
const {
message,
title
} = item
return (
<div key={idx} className="message-content">
<p>{title}</p>
<p>{message}</p>
</div>
)
})}
</div>
One approach would be to group items of arrayOfMessages by the title field, to achieve the required rendering result by using the native .reduce() method:
const arrayOfMessages=[
{
title: 'cars',
message: 'toyota'
},
{
title: 'cars',
message: 'ford'
},
{
title: 'cars',
message: 'bmw'
},
{
title: 'bikes',
message: 'suzuki'
},
{
title: 'bikes',
message: 'bmw'
},
];
/* Use reduce() to group items of arrayOfMessages by title */
const groupedMessages = arrayOfMessages.reduce((groups, item) => {
/* Find group for the title of current item */
const group = groups.find(group => group.title === item.title);
/* If matching group found, add message of item to it's messages array */
if(group) {
group.messages.push(item.message);
}
/* Otherwise, add a new group for this title */
else {
groups.push({ title : item.title, messages : [] });
}
return groups;
}, [])
console.log(groupedMessages);
Using the code above, you could then revise your render() method to render the title once for each item category:
<div>
{ groupedMessages.map((group, idx0) => (<div key={idx0} className="message-content">
<h2>{ group.title }</h2>
{ group.messages.map((message, idx1) => (<p key={idx1}>{message}</p>)) }
</div>))
}
</div>
Hope that helps!
Your object is not well structured, should be like this:
arrayOfMessages=[
{
title:"cars",
description:["toyota","ford","bmw"]
},
{
title:"bike",
description:["suzuki","bmw"]
}
]
then you can implement your code like this :
{
arrayOfMessages.map((message, idx) => {
const { description, title } = message
return (
<div key={idx} className="message-content">
<p>{title}</p>
{description.map((desc, index) => (
<p key={index}>desc</p>
))}
</div>
)
})
}
You should make up your data first.
...
let renderMessages = {}
arrayOfMessages.forEach(message => {
if (!renderMessages[message.title]) {
renderMessages[message.title] = {
title: message.title,
messages: [message.message]
}
} else {
renderMessages[message.title].messages.push(message.message)
}
})
return (
<div>
{Object.keys(renderMessages).map(key => {
let msg = renderMessages[key]
return <div key={key} className="message-content">
<p>{msg.title}</p>
{msg.messages.map(content => <p key={content}>{content}</p>)}
</div>
})}
</div>
)
You could group the messages based on the title using reduce. Then loop through the entries of the merged object to get the desired format
const arrayOfMessages=[{title:'cars',message:'toyota'},{title:'cars',message:'ford'},{title:'cars',message:'bmw'},{title:'bikes',message:'suzuki'},{title:'bikes',message:'bmw'},]
const grouped = arrayOfMessages.reduce((acc, { title, message }) => {
acc[title] = acc[title] || [];
acc[title].push(message);
return acc;
}, {})
console.log(grouped)
Here's a live demo:
class Sample extends React.Component {
render() {
const arrayOfMessages = [{ title: 'cars', message: 'toyota' }, { title: 'cars', message: 'ford' }, { title: 'cars', message: 'bmw' }, { title: 'bikes', message: 'suzuki' }, { title: 'bikes', message: 'bmw' },]
const grouped = arrayOfMessages.reduce((acc, { title, message }) => {
acc[title] = acc[title] || [];
acc[title].push(message);
return acc;
}, {})
return (
<div>
{
Object.entries(grouped).map(([title, messages], i1) => (
<div key={i1}>
<h1>{title}</h1>
{ messages.map((message, i2) => (<p key={i2}>{message}</p>)) }
</div>)
)
}
</div>
);
}
}
// Render it
ReactDOM.render(
<Sample />,
document.getElementById("react")
);
<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="react"></div>
try this
render(){
//title watcher
let watcher = {};
return <div>
{arrayOfMessages.map((item, idx) => {
const {
message,
title
} = item
if(!watcher[title]){
//update watcher with title
//if watcher does not contain your title return title and message
watcher[title]=title;
return (
<div key={idx} className="message-content">
<p>{title}</p>
<p>{message}</p>
</div>
)
} else {
//if watcher found with title return your message
<div key={idx} className="message-content">
<p>{message}</p>
</div>
}
})}
</div>
}

Reactjs having problems printing out fetched data

super noob question. However, I've been stuck with this issue for long enough now. I've been searching and have found the similar issue but have not been able to resolve it. I was hoping someone could point me in the right direction as to what I am doing wrong.
I am fetching data from an API, and I am able to log out the array, however, when I try to print it out I am having no luck. In the beginning, I was passing it through a reducer but then realized that was not ideal. Right now I am trying to get it to work, then I'll refactor the code into a functional component.
fetchOrders = async () => {
await axios.get('http://localhost:4200/ordenes').then( res => {
this.setState({
prescription: res.data
})
})
}
render() {
console.log(this.state.prescription)
return (
<div >
<Paper style={styles.orderCard}id='orderCardContainer' >
<div id="profileCardContainer" style={styles.profileCard}>
<aside className='profileCardContainer'>
<ProfileCard />
</aside>
</div>
{this.renderOrder()}
</Paper>
</div>
)
}
}
The array I receive back from the API looks something like this:
Array(18)
0: {order: Array(3), _id: "5b2af38fb315eb5630a0bc78", physicianName: "Alejandro", patientName: "Alex", createdAt: "2018-06-21T00:38:39.376Z", …}
UPDATE:
class OrderCard extends Component {
constructor(props) {
super(props)
this.state = { prescription: []}
}
fetchOrders = async () => {
const res = await axios.get('http://localhost:4200/ordenes')
this.setState({
prescription: res.data
})
}
componentDidMount() {
this.fetchOrders()
}
renderOrder() {
if (this.state.prescription.length === 0 ) {
return (
<h1>Loading...</h1>
)
} else {
return (
this.state.prescription.map( (x, i) => {
<li>
{console.log(x.patientName)}
<h1>{ x.patientName }</h1>
</li>
})
)}
}
render() {
console.log(this.state.prescription)
return (
<div >
<Paper style={styles.orderCard}id='orderCardContainer' >
<div id="profileCardContainer" style={styles.profileCard}>
<aside className='profileCardContainer'>
<ProfileCard />
</aside>
</div>
<div id='orders' >
{ this.renderOrder() }
</div>
</Paper>
</div>
)
}
}
function mapStateToProps(state) {
return {
auth: state.auth.authenticated,
}
}
export default connect(mapStateToProps, actions)(OrderCard)
BACKEND SCHEMA
const orderObj = new Schema({
name: String,
price: Number,
}, { _id : false })
const orderSchema = new Schema (
{
physicianName: String,
patientName: String,
order: {type: [orderObj]}
},
{
timestamps: true
}
)
POSTMAN API RESULTS
[
{
"order": [
{
"name": "Asteraceae",
"price": 41.24
},
{
"name": "Liliaceae",
"price": 39.24
},
{
"name": "Fabaceae",
"price": 34.91
}
],
"_id": "5b2af38fb315eb5630a0bc78",
"physicianName": "Alejandro",
"patientName": "Alex",
"createdAt": "2018-06-21T00:38:39.376Z",
"updatedAt": "2018-06-21T00:38:39.376Z",
"__v": 0
}
]
Thanks in advance for tips.
Wrong brackets, you're not returning react element, try fixed one.
renderOrder() {
if (this.state.prescription.length === 0 ) {
return (
<h1>Loading...</h1>
)
} else {
return (
this.state.prescription.map((x, i) => (
<li>
{console.log(x.patientName)}
<h1>{ x.patientName }</h1>
</li>
))
)}
}
and fix this
fetchOrders = async () => {
const res = await axios.get('http://localhost:4200/ordenes');
this.setState({
prescription: res.data
});
}

Categories

Resources