React: Updating multiple object variables in array - reactjs

I want to implement a voting system in my app where you can vote thumbs-up or thumbs-down for a selected movie. The ratings will be saved in a ratings array of objects that contain movie title, id, thumbs-up votes and thumbs-down votes. If a movie has no votes, the first vote will add the new object into the ratings array, and subsequent votes will update the thumbs-up votes and thumbs-down votes of the object. My current code works for the first thumbs-up vote and adds the new obj into the array, but does not update the vote count for subsequent votes. How can I update the votes in the movie object for both thumbs-up and thumbs-down votes? This involves incrementing the count depending on whether the thumbs-up or thumbs-down button is clicked. How can I handle this besides creating separate but similar functions for each button like I’ve already started to do? Any help is greatly appreciated, thank you in advance!
const ThumbRating = ( {id, title} ) => {
const [thumbsUpCount, setthumbsUpCount] = useState(0);
const [thumbsDownCount, setthumbsDownCount] = useState(0);
const [ratings, setRatings] = useState([]);
const newUpVote = (id) => {
if (thumbsUpCount === 0 && thumbsDownCount === 0) {
setthumbsUpCount(thumbsUpCount + 1);
const obj = {
id: {id},
title: {title},
thumbsUpCount: thumbsUpCount +1,
thumbsDownCount: thumbsDownCount
}
setRatings([obj])
} else {
setthumbsUpCount(thumbsUpCount +1)
handleThumbsUp(id, thumbsUpCount +1)
}
}
const handleThumbsUp = (id, thumbsUpCount) => {
setRatings(ratings.map(obj => {
if (obj.id !== id) return obj
return {...obj, thumbsUpCount: thumbsUpCount +1}
}))
}
return (
<div className="thumb-rating">
<p>Would you recommend this movie?</p>
<table>
<tbody>
<tr>
<td>
<div >
<button className="thumbs-up" onClick={() => newUpVote(id)}>
<i className="fa fa-thumbs-up fa-4x" />
</button>
</div>
</td>
<td>
<div >
<button className="thumbs-down" onClick={() => setthumbsDownCount(thumbsDownCount + 1)}>
<i className="fa fa-thumbs-down fa-4x" />
</button>
</div>
</td>
</tr>
<tr>
<td>
<h2>Yes: {thumbsUpCount} </h2>
</td>
<td>
<h2>No: {thumbsDownCount} </h2>
</td>
</tr>
</tbody>
</table>
</div>
)
}

There is My solution:
The handlerHandleCountDown is working and the state is correcting updating.
Now you can try to handle the countDown based on the example.
Do not forget to pass id and title as props to your component. 🙂
import { useState } from 'react'
const ThumbRating = ({ id, title }) => {
const [thumbsUpCount, setthumbsUpCount] = useState(0)
const [thumbsDownCount, setthumbsDownCount] = useState(0)
const [ratings, setRatings] = useState([])
const newUpVote = (id) => {
if (thumbsUpCount === 0 && thumbsDownCount === 0) {
setthumbsUpCount(thumbsUpCount + 1)
const obj = {
id, // id:id also works but id: {id} will not
title,
thumbsUpCount: thumbsUpCount + 1,
thumbsDownCount: thumbsDownCount,
}
setRatings([obj])
} else {
setthumbsUpCount(thumbsUpCount + 1)
handleThumbsUp(id)
}
}
const handleThumbsUp = (id) => {
// Is better to use the find method when you want to find a single element in an array
const objectToBeUpdated = ratings.find((obj) => obj.id === id)
objectToBeUpdated.thumbsUpCount += 1 // I assumed you will always update by one
setRatings([objectToBeUpdated])
}
return (
<div className='thumb-rating'>
<p>Would you recommend this movie?</p>
<table>
<tbody>
<tr>
<td>
<div>
<button className='thumbs-up' onClick={() => newUpVote(id)}>
<i className='fa fa-thumbs-up fa-4x' />
</button>
</div>
</td>
<td>
<div>
<button
className='thumbs-down'
onClick={() => setthumbsDownCount(thumbsDownCount + 1)}
>
<i className='fa fa-thumbs-down fa-4x' />
</button>
</div>
</td>
</tr>
<tr>
<td>
<h2>Yes: {thumbsUpCount} </h2>
</td>
<td>
<h2>No: {thumbsDownCount} </h2>
</td>
</tr>
</tbody>
</table>
</div>
)
}
export default ThumbRating

Related

How to use local storage in React js

This is my code:
function EditCourseTable() {
const [data, setData] = useState([]);
const [CourseID, setCourseID] = useState(0);
useEffect(() => {
Axios
.get("http://localhost:3003/coursestable")
.then(result => setData(result.data));
}, []);
return (
<div className="main">
<h2>
<table className="table" >
<thead className="thead-dark">
<tr>
<th scope="col">Course Number</th>
<th scope="col">Course Name</th>
<th scope="col">View Details</th>
<th scope="col">Edit Course</th>
<th scope="col">Delete Course</th>
</tr>
</thead>
<tbody>
{data.map((item, id) => {
return <tr key={id}>
{localStorage.setItem('CourseID', item.CourseID)}
<td>{item.CourseID}</td>
<td>{item.Name}</td>
<td>View</td>
<td><a href={`/editcourse2`} className="btn btn-primary" >
Edit</a></td>
<td><button className="btn btn-primary">Delete</button></td>
</tr>
})}
</tbody>
</table>
</h2>
</div>
)
}
export default EditCourseTable;
I use the localStorage to store the CourseId that the user click on (when click in Edit or View), but it is store the last courseID in the table, not the courseID that I click on. Whats the error?
You should insert the value in the localStorage by triggering a function called on click of an element
function storeCourse(id) {
localStorage.setItem('CourseID', id)
}
<td>
<span
className="btn btn-primary"
onClick={() => storeCourse(item.CourseID)}>
View
</span>
</td>
You need to create something to catch that click, so you can create some function like
const handleClickItem = (courseID) => {
localStorage.setItem('CourseID', courseID)
}
So whenever the user click, it will use onClick, so you can pass something like onClick = { () => handleClickItem(item.CourseID)} then pass the item.CourseID into that handleClickItem
Now the handleClickItem has the courseID
That's when you localStorage.setItem('CourseID', item.CourseID)
function EditCourseTable() {
const [data, setData] = useState([]);
const [CourseID, setCourseID] = useState(0);
useEffect(() => {
Axios
.get("http://localhost:3003/coursestable")
.then(result => setData(result.data));
}, []);
//- Add handleClickItem
const handleClickItem = (courseID) => {
localStorage.setItem('CourseID', courseID)
}
return
Inside the return, the map one, just add onClick where ever you want the user to click
for example:
<tr key={id} onClick = {() => handleClickItem(item.CourseID)}>
Your localStorage code runs when rendered so the last rendered item's id is saved to localStorage. You should use the function onClick.
<tbody>
{data.map((item, id) => {
return <tr key={id} onClick={() => localStorage.setItem('CourseID', item.CourseID)}>
<td>{item.CourseID}</td>
<td>{item.Name}</td>
<td>View</td>
<td><a href={`/editcourse2`} className="btn btn-primary" >
Edit</a></td>
<td><button className="btn btn-primary">Delete</button></td>
</tr>
})}
</tbody>
In your code you save data to local storage on items render. All items saves to local storage on key CourseID in render order.
Because of this after items render local storage CourseID value equal last item in rendered collection.
Right chose for solving this problem is saving data to local storage on link click.
But i think you does not need saving this data to local storage. React allow storing this in state.
Example for your code:
const [clickedCourseId, setClickedCourseId] = useState(null);
...
render (
...
{data.map((item, id) => {
return (
<tr key={id}>
<td>{item.CourseID}</td>
<td>{item.Name}</td>
<td>View</td>
<td><a href={`/editcourse2`} onClick={() => { setClickedCourseId(item.CourseID) }} className="btn btn-primary" >
Edit</a></td>
<td><button className="btn btn-primary">Delete</button></td>
</tr>
)
})}
In this example, when you click on View or Edit links, clickedCourseId being filled clicked item CourseId and you does not need to store it in localStorage.
However, if you want to store it in localStorage, you can change setClickedCourseId to your localStorage setItem

Item in array getting deleted from top only

I have an array off which I would like to delete elements upon clicking the delete button. However, the problem with this is that only the data at the gets deleted no matter where I click leaving the data at that index intact. I would like to know what I can do to ensure this works normally. Below is my code:
import { useEffect, useState } from "react";
const Task = () => {
const [todos, setTodos] = useState([]);
useEffect(() => {
fetch('http://jsonplaceholder.typicode.com/todos')
.then(res => res.json())
.then(data => {
setTodos(data)
})
}, []);
//Using splice
// const deleteTodo = (index) => {
// const temp = [...todos];
// temp.splice(index, 1);
// setTodos(temp);
// console.log(index);
// // console.log(`newData : ${arr} || newLength : ${arr.length}`);
// console.log(`newLength : ${todos.length}`);
// }
//Using Filter
const deleteTodo = (index) => {
const newTodo = todos.filter(todo => todo.id !== index);
setTodos(newTodo);
console.log(`newLength : ${todos.length}`);
}
return (
<div className='container'>
<table className='table'>
<tbody>
{todos.map((key, value) => (
<tr key={key.id}>
<td>{todos[value].id}</td>
<td>{todos[value].title}</td>
<td>{`${todos[value].completed}`}</td>
<td>
<button className='btn btn-danger ' onClick={() => deleteTodo(key)}> Delete </button>
</td>
</tr>
))}
</tbody>
</table>
<button className='btn btn-primary'>Add Task</button>
</div>
);
}
export default Task;
I have tried both the splice and the filter methods.
The splice method deletes data only off the top irrespective of the data I delete whereas the filter method doesn't do anything at all as shown on the snippet below. The length remains the same even after clicking the delete button.
In .map() method the first argument - current array item, the second - index. You pass the current array item to your deleteTodo func, instead of passing id (deleteTodo(key.id)).
It should be like this:
const deleteTodo = (index) => {
const newTodo = todos.filter(todo => todo.id !== index);
setTodos(newTodo);
console.log(`newLength : ${todos.length}`);
}
return (
<div className='container'>
<table className='table'>
<tbody>
{todos.map((key, value) => (
<tr key={key.id}>
<td>{todos[value].id}</td>
<td>{todos[value].title}</td>
<td>{`${todos[value].completed}`}</td>
<td>
<button className='btn btn-danger ' onClick={() => deleteTodo(key.id)}> Delete </button>
</td>
</tr>
))}
</tbody>
</table>
<button className='btn btn-primary'>Add Task</button>
</div>
);
Also you don't need to do todos[value] as you already have a current item.
You could use this:
todos.map((item, index) => (<>
<td>{item.id}</td>
<td>{item.title}</td>
</>)

componenet does not re-render after change in state in react.js

i have an eCommerce application i want the quantity of an item in shopping cart** to be increased as user clicks on plus icon , but when the user clicks on the item state changes but the component doesn't re-renders there for not updating the quantity number in shopping cart , but as i refresh the page the number is updated
main code:
const [item, setItems] = useState([]);
useEffect(() => {
setItems(items);
}, []);
const handlePlus = (number, index) => {
let Nnumber = (number += 1);
let changeitems = item;
changeitems[index].qty = Nnumber;
setItems(changeitems);
localStorage.setItem("items", JSON.stringify(changeitems));
};
JSX:
<span onClick={(e) => {
handlePlus(eachItem.qty, index);
}}
>
<i className="bi bi-plus-circle-fill text-success"></i>{" "}
</span>
Complete code
import { Fragment, useState, useEffect } from "react";
const Cartitemscreater = ({ items ,cartUpdater}) => {
const [total, setTotal] = useState(0);
const [item, setItems] = useState([]);
const [available,setAvailable] = useState(true)
useEffect(() => {
setItems(items);
let totalPr = 0;
for (let index = 0; index < items.length; index++) {
let price = parseInt(items[index].productprice);
totalPr += price * items[index].qty;
}
setTotal(totalPr);
}, []);
let changeItems = []
const handlePlus = (number, index) => {
let Nnumber = (number += 1);
changeItems = item;
changeItems[index].qty = Nnumber;
setItems(changeItems);
localStorage.setItem("items", JSON.stringify(changeItems));
};
const handleMinus = (number, index) => {
let Nnumber = (number -= 1);
let changeitems = item;
changeitems[index].qty = Nnumber;
setItems(changeitems);
localStorage.setItem("items", JSON.stringify(changeitems));
};
const handleDelete = (_id,index)=>{
let deleteState = []
for (let index = 0; index < item.length; index++) {
if(item[index]._id!==_id){
deleteState.push(item[index])
}
}
setItems(deleteState)
cartUpdater(deleteState.length)
if(deleteState.length===0){
setAvailable(false)
return localStorage.removeItem("items")
}
localStorage.setItem("items",JSON.stringify(deleteState))
}
return (
<Fragment>
{ available ?<div className="container my-5">
<div className="table-responsive-xl">
<table class="table mt-5 ">
<thead>
<tr>
<th scope="col">Product image</th>
<th scope="col">Product title</th>
<th scope="col">Product price</th>
<th scope="col">Product quantity </th>
</tr>
</thead>
{ item.map((eachItem, index) => {
return (
<tbody key={eachItem._id} className="shadow">
<tr>
<td>
<img
src={`/products/${eachItem._id}/${eachItem.prImage}`}
className="img-fluid imgs "
alt="somethings"
style={{
width: "130px",
height: "80px",
objectFit:"cover"
}}
/>
<p><small className="text-muted">{eachItem.productdescription}</small></p>
<span onClick={()=>{
handleDelete(eachItem._id,index)
}}><i class="bi bi-trash fs-4 text-danger"></i></span>
</td>
<td>{eachItem.producttitle}</td>
<td className="fw-bold">$ {eachItem.productprice}</td>
<td>
<span
onClick={(e) => {
handleMinus(eachItem.qty, index);
}}
>
{" "}
<i className="bi bi-dash-circle-fill text-danger"></i>
</span>
<span className="mx-2"> {eachItem.qty}</span>
<span
onClick={(e) => {
handlePlus(eachItem.qty, index);
}}
>
<i className="bi bi-plus-circle-fill text-success"></i>{" "}
</span>
</td>
</tr>
</tbody>
);
})}
</table>
</div>
</div>: <p className="mt-lg-5 fw-bold text-danger fs-4"> {"Cart is empty "}<i class="bi bi-cart-fill"></i> </p> }
</Fragment>
);
};
export default Cartitemscreater;
The number isn't updating because useState is async. React is not updating instant instead it is just putting the state update into a queue to avoid unnecessary re-renders. If you really need the update you can use this update function.
Example:
const useForceUpdate = () => {
const [value, setValue] = useState(0);
return () => setValue((value) => value + 1);
};
Just call it in the same click event.

Get a value from a <i> tag with hooks in react

I'm trying to get a value from i tag with hook. When I click on it but in console, the result is undefined.
Here is my code snippet.
const deleteCar = event => {
const car_id = event.target.value
console.log(car_id)
}
<tbody>
{
props.cars && props.cars.map( car =>
(
<tr key={car.id}>
<td>{car.model}</td>
<td><i className="far fa-trash-alt" name="car_id" value={car.id} onClick={deleteCar}></i><i className="far fa-trash-alt"></i></td>
</tr>
)
)}
</tbody>
How can I do that?
Thanks!
I think the issue is that you are not passing the event in your function onClick. It should be: onClick={(event) =>deleteCar(event) }
You can do this in two ways. But both will need a state to hold the car.id for you. (**Reminder: since you are using <i> tag, you can't just put value as prop. That's for <input> tag only)
Using React Hook (useEffect) - better option (proven to work every time):-
const [carId, setCarId] = useState('')
const [isDelete, setIsDelete] = useState(false)
// handle delete of car
const deleteCar = () => {
if(isDelete) {
// perform delete
console.log(carId)
// reset states involve
setCardId('')
setIsDelete(false)
}
}
// invoke delete function
useEffect(()= > {
deleteCar()
}, [isDelete])
<tbody>
{props.cars && props.cars.map( car => (
<tr key={car.id}>
<td>{car.model}</td>
<td>
<i
className="far fa-trash-alt"
onClick={() => {
setCarId(car.id)
setIsDelete(true)
}}
>
</i>
<i className="far fa-trash-alt"></i>
</td>
</tr>
))}
</tbody>
Just use normal onClick function:-
const [carId, setCarId] = useState('')
// handle delete of car
const deleteCar = () => {
// perform delete
console.log(carId)
// reset states involve
setCardId('')
}
<tbody>
{props.cars && props.cars.map( car => (
<tr key={car.id}>
<td>{car.model}</td>
<td>
<i
className="far fa-trash-alt"
onClick={() => {
setCarId(car.id)
deleteCar()
}}
>
</i>
<i className="far fa-trash-alt"></i>
</td>
</tr>
))}
</tbody>
if it was an input tag for example you could access its value attribute through event.target.value, but that not the case for icon tag. Use the getAttribute method in order to access it:
const deleteCar = event => {
const car_id = event.target.getAttribute('value')
console.log(car_id)
}

Sort the table ascending & descending order when i click on button in reactjs

function TopicsTable(props) {
return (
<Table className="tablecss">
<thead>
<tr>
<th>
<button type="button">
{props.getHeader[0]}
</button>
</th>
<th>
<button type="button">
{props.getHeader[1]}
</button>
</th>
<th>
<button type="button">
{props.getHeader[2]}
</button>
</th>
<th>
<button type="button">
{props.getHeader[3]}
</button>
</th>
<th>
<button type="button">
{props.getHeader[4]}
</button>
</th>
<th>
<button type="button">
{props.getHeader[5]}
</button>
</th>
</tr>
</thead>
<TableBody>
{(props.getRowInfo)}
</TableBody>
</Table>
);
}
getRowsData = () => {
var rowContents = this.state.data;
return rowContents.map((rowContent, index) => {
this.state.rowCount = this.state.rowCount + 1;
return <React.Fragment><TableRow key={index} onClick={() => this.handleClick(index)}>{this.RenderCell(rowContent)}</TableRow> {this.state.show && this.state.id === index ? <ChildComponent/>:""} </React.Fragment>
})
}
render() {
return (
<div className="Main">
<header className="App-header">
<h2 className="hdr"><i>Openion</i></h2>
</header>
<h4><Actionunit /></h4>
<br />
<TopicsTable getHeader={this.getHeader()} getRowInfo={this.getRowsData()}/>
</div>
);
}
Sort the table ascending & descending order when i click on button in reactjs
I think the requirements you're looking for is:
dynamically select which column is to be sorted
sort ascending or descending
the number of columns can be dynamic
the number of rows can be dynamic
My solution doesn't use <table> but that is something you can manage easily...
Relevant component which gets headers, rows... sorts and prints data:
import React, { useState, useEffect } from "react";
export const MyTable = props => {
const { header, data } = props;
const [sortedRows, setSortedRows] = useState([]);
const [headers, setHeaders] = useState([]);
useEffect(() => {
setSortedRows(data);
setHeaders(header);
}, []);
const Sorter = fieldToSort => {
let sortedArray = sortedRows;
if(fieldToSort.dir === 'asc' ){
sortedArray.sort((a, b) =>
a[fieldToSort.label] > b[fieldToSort.label] ? 1 : b[fieldToSort.label] > a[fieldToSort.label] ? -1 : 0
);
} else {
sortedArray.sort((a, b) =>
a[fieldToSort.label] < b[fieldToSort.label] ? 1 : b[fieldToSort.label] < a[fieldToSort.label] ? -1 : 0
);
}
(fieldToSort.dir === 'asc') ? fieldToSort.dir = 'dsc' : fieldToSort.dir = 'asc';
let newHeaders = header;
newHeaders[fieldToSort.dir] = fieldToSort.dir;
setHeaders([...newHeaders]);
setSortedRows([...sortedArray]);
};
return (
<>
<h2>Actual Table</h2>
{headers.map((val, ind) => {
return (
<button type="button" key={ind} onClick={() => Sorter(val)}>
{val.label} ({val.dir})
</button>
);
})}
{sortedRows.length > 0 ? (
<ul>
{sortedRows.map((val, ind) => {
return (
<li>
{val.name} - {val.age}{" "}
</li>
);
})}{" "}
</ul>
) : (
"no data available"
)}
</>
);
};
complete working stackblitz here
First, define your state, to save your current sort order:
state = {
sortedBy: '',
sortDirection: 0
}
Then, define constants, to show the order direction (outside of your class):
const sortAsc = 1;
const sortDesc = -1;
Then, define your sort function:
sortTable = (columnName) => {
let sortDirection = sortAsc;
if (this.state.columnName === columnName) {
if (this.state.sortDirection === sortAsc) {
sortDirection = sortDesc;
}
}
this.s.TableData.sort((x1, x2) => x1[columnName] < x2[columnName] ? -1 * sortDirection : sortDirection )
this.setState({
columnName, sortDirection, data: this.state.data
})
};
Then, attach this function to each table header:
<th>
<button type="button" onClick={() => props.sortTable(props.getHeader[0])> // <-- here you pass the header NAME (Upvote, Downvote, Comment and etc.)
{props.getHeader[0]}
</button>
</th>
Keep in mind, that this code is taken out of context, and you may face errors with naming, parameters passing and etc, in general it is a basic abstraction. The sort function was tested out and working. I hope you will manage to integrate it in your application. Good luck!

Resources