I'm writing a program where you can add reviews to a list in React. I also added a feature to delete the reviews. Each review is a component stored in a State array. I wrote a function, removeItem, that updates the state by creating a duplicate of the array and popping the passed index". Each review is given a handleFeature property where this removeItem function is passed, and an id which corresponds to it's index in the array.
Inside the review component, it has an onclick event which calls the removeItem function through handleFeature, passing it's own id in the array. I thought this would cause the array to update and remove the item; however, It causes multiple items to get deleted for no apparent reason. Does anyone know the fix to this issue
Data
export default[
{
id: 0,
name: "Mindustry",
score: "4.5"
},
{
id: 1,
name: "Minecraft",
score: "4"
},
{
id: 2,
name: "Plants vs Zombies",
score: "4"
},
]
App
import './App.css';
import jSData from './components/jSData.js'
import Card from './components/Card.js'
import React from 'react';
function App() {
//we are mapping the review data to an array of cards using an lamba expression
//this is a state object. Once it changes, the webpage is updated
//it returns the object and a function to change it
//the object is immutable; however, you can reference it to make updates
const [reviews, changeState] = React.useState(jSData.map((item) => {
return (<Card
//key is necessary for list items
key = {item.id}
handleEvent = {removeItem}
//name = {item.name}
//score = {item.score}
//the above can be simplified to
{...item}
/>);
}
));
function submit(e)
{
//prevent reloading
e.preventDefault();
//spreading the original array card into a new array
/*changeState(
[...reviews,
<Card
id = {reviews.length}
name = {document.getElementById('form_name').value}
score = {document.getElementById('form_score').value}
/>]
);*/
//best practice to use the higher order version of state change
//this should contain a function which returns the new state
changeState(oldValue =>
[...oldValue,
<Card
id = {reviews.length}
key = {reviews.length}
handleEvent = {removeItem}
name = {document.getElementById('form_name').value}
score = {document.getElementById('form_score').value}
/>]
);
}
function removeItem(id)
{
changeState(reviews.map(x => x).pop(id))
}
return (
<div id = "wrapper">
<form id = "review-form">
<h1>Review</h1>
<input className = "review-text" placeholder="Name" id = "form_name"/>
<input className = "review-text" placeholder="Score" id = "form_score"/>
<input id = "review-button" type = "Submit" onClick = {submit}/>
</form>
<ul id = "card-holder">
{reviews}
</ul>
</div>
);
}
export default App;
Review Component
import React from "react";
export default function Card(item)
{
function handle()
{
console.log(item.handleEvent);
item.handleEvent(item.id)
}
//conditional rendering with and statements
return(
<div className = "card-wrapper">
<div className = "card">
<h2>{item.name}</h2>
<h4>{item.score} / 5</h4>
</div>
<span onClick = {handle}>close</span>
</div>
);
}
CodePudding user response:
Here's a simplified and fixed example. As I said in the comment, don't put elements in state; just map your data into elements when returning your markup.
- The initial data is returned by a function, so we dont accidentally mutate "static data" from another module
- The
Card
component now accepts the item as a prop, not "spread out" - Removal actually works (we filter the state array so there's only items without the ID-to-remove left)
import React from "react";
function getInitialData() {
return [
{
id: 0,
name: "Mindustry",
score: "4.5",
},
{
id: 1,
name: "Minecraft",
score: "4",
},
{
id: 2,
name: "Plants vs Zombies",
score: "4",
},
];
}
function Card({ item, onRemove }) {
return (
<div className="card-wrapper">
<div className="card">
<h2>{item.name}</h2>
<h4>{item.score} / 5</h4>
</div>
<span
className="material-symbols-outlined"
onClick={() => onRemove(item.id)}
>
close
</span>
</div>
);
}
function App() {
const [data, setData] = React.useState(getInitialData);
function removeItem(id) {
setData((reviews) => reviews.filter((item) => item.id !== id));
}
function submit(e) {
e.preventDefault();
setData((reviews) => {
// TODO: do these with refs or state instead of getElementById
const name = document.getElementById("form_name").value;
const value = document.getElementById("form_score").value;
const newReview = {
id: ( new Date()).toString(36),
name,
value,
};
return [...reviews, newReview];
});
}
return (
<div id="wrapper">
<form id="review-form">
<h1>Review</h1>
<input
className="review-text"
placeholder="Name"
id="form_name"
/>
<input
className="review-text"
placeholder="Score"
id="form_score"
/>
<input id="review-button" type="Submit" onClick={submit} />
</form>
<ul id="card-holder">
{data.map((item) => (
<Card key={item.id} item={item} onRemove={removeItem} />
))}
</ul>
</div>
);
}