I'm trying to splice on a DoubleClick event an item from my list(inventory) but something is not working as I want it. Would be nice if someone could be so kind and help me out to figure out what I'm doing wrong here. For the Info: when I try to slice an item it gets sliced but it is every time the first item and the second item loses all the content inside:
function Inventory() {
const [datas, setData] = useState([
{
id: 1,
label: "Sword",
itemname: "grey_sword",
pieces: 1,
type: "weapon",
},
{
id: 2,
label: "Knife",
itemname: "grey_knife",
pieces: 1,
type: "weapon",
},
]);
useEffect(() => {
const getItems = (data) => {
setData(data);
} // this section is for something else
}, [datas]);
const deleteItem = (index) => {
const test = ([], datas)
test.splice(index, 1)
setData([{test : datas}]);
}
const renderItem= (data, index) => {
return (
<Item
key={data.id}
id={data.id}
type={data.type}
label={data.label}
index={index}
name={data.itemname}
pieces={data.pieces}
deleteItem={deleteItem}
/>
)
}
return (
<div className="inventory-holder">
<div className="inventory-main">
<div className="inventory-information">
<div className="inventory-title">
Inventory
</div>
<div className="inventory-weight-info">
0.20 kg / 1.00 kg
</div>
<div className="inventory-buttons">
<button className="refresh-button" tabIndex="-1"><FontAwesomeIcon icon={faRedo} /></button>
<button className="close-button" tabIndex="-1"><FontAwesomeIcon icon={faTimes} /></button>
</div>
</div>
<div className="items-holder">
<div>{datas.map((data, i) => renderItem(data, i))}</div>
</div>
</div>
</div>
)
}
export default Inventory;
and that would be the Item:
const Item = ({ index, id, type, label, name, pieces, deleteItem }) => {
const useItem = e => {
const type = e.target.getAttribute("itemType");
const index = e.target.getAttribute("data-index");
const pieces = e.target.getAttribute("itempieces");
console.log(type + " " + index + " " + pieces )
if(parseInt(pieces) <= 1){
deleteItem(parseInt(index));
}
}
return(
<div data-index={id} onDoubleClick={useItem} className="inventory-item" itemType={type} itemname={name} itemlabel={label} itempieces={pieces}>
<img className="item-pic" src={chosedtype} ></img>
<div className="item-label">{label}</div>
<div className="item-number-pieces">{pieces}</div>
</div>
);
};
export default Item;
Issue
Array::splice does an in-place mutation.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place.
The main issue here is state mutation.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
const test = ([], datas) ends up saving the state reference data to test, which is then mutated by test.splice(index, 1), and then strangely enough is overwritten back into state differently setData([{ test: datas }]);.
Solution
A common pattern is to use array::filter instead and filter on the index. filter returns a new array.
const deleteItem = (index) => {
setData(datas => datas.filter((_, i) => i !== index);
}
Your Item is way too complicated:
You can directly use the props without passing them through your div.
const Item = ({ index, id, type, label, name, pieces, deleteItem }) => {
const useItem = () => {
console.log(type + " " + index + " " + pieces )
if(pieces <= 1){
deleteItem(index);
}
}
return(
<div data-index={id} onDoubleClick={useItem} className="inventory-item">
<img className="item-pic" src={chosedtype} ></img>
<div className="item-label">{label}</div>
<div className="item-number-pieces">{pieces}</div>
</div>
);
};
export default Item;
Then your deleteItem function doesn't do what you want:
const deleteItem = (index) => {
const test = ([], datas); //test = datas;
test.splice(index, 1); // test = test without the item at the index
setData([{test : datas}]);//data = [{test: datas}] so an array with an object with the property test = datas (the original array).
}
You should change your deleteItem to something like:
const deleteItem = (index) => {
const newArray = datas.filter((item, i) => i !== index);
setData(newArray);
}
Related
When the user adds a value to an object that is inside an array the status is updated to added.
I'm trying to do the same thing when that value is deleted, to update the status to deleted.
const initialName = [
{
name: "",
status: "",
},
];
export default function changeNames(){
const [name, setName] = useState([initialName ]);
const handleAdd = () => {
setName([...name, ""]);
};
const handleItemChanged = (event, index) => {
const value = event.target.value;
const list = [...name];
list[index] = { name: value + "-" + id, status: "added" };
setName(list);
};
...
}
So when I add an input field and type the name, the array looks like this:
[{…}]
0:
name: "John-id"
status: "added"
When I remove John from the array, I want smth like this:
[{…}]
0:
name: "John-id"
status: "deleted"
This is the remove function
const handleRemoveClick = (index) => {
const list = [...name];
list.splice(index, 1);
setName(list);
};
<div>
{name.map((o, i) => {
return (
<tr key={"item-" + i}>
<td>
<div>
<input
type="text"
value={o.item}
onChange={(event) => handleItemChanged(event, i)}
placeholder="Name it"
/>
</div>
</td>
<td>
<button type="button" onClick={handleRemoveClick}>
Delete
</button>
</td>
</tr>
);
})}
<div>
<button Click={handleAddClick}>
Add Language
</button>
</div>
</div>
);
How can I make it work?
I think this might help you.thank you
import { useState } from "react";
import "./styles.css";
export default function App() {
const [Name, setName] = useState([]);
const [input, setInput] = useState({ name: "", status: "" });
const handleItemChanged = (event, index) => {
const { value } = event.target;
setInput({ name: value + "-id", status: "added" });
};
const addHandler = () => {
setName((prev) => {
return [...prev, input];
});
};
const removeHandler = (i) => {
const arr = [...Name];
arr[i].status = "deleted";
setName(arr);
};
return (
<div className="App">
<input type="text" name="name" onChange={(e) =>
handleItemChanged(e)} />
<button onClick={addHandler} style={{ margin: "1rem" }}>
Add
</button>
<div>
{Name &&
Name.map((data, i) => {
return (
<div key={i}>
<h3>
{data.name} {data.status}
<button
style={{ margin: "1rem" }}
onClick={() => removeHandler(i)}
>
Delete
</button>
</h3>
</div>
);
})}
</div>
</div>
);
}
You need to change the "status" property, because there is no way of removing item and setting its property.
const handleRemoveClick = (event, index) => {
const list = [...name];
list[index].status = "deleted";
setName(list);
};
And later while rendering you have to either perform a check in map function (not really elegant), or first filter your array and then map it:
// first, not elegant in my opinion
...
{name.map(item => item.status !== "deleted" ? <div>item.name</div> : null)}
// second approach
...
{name.filter(item => item.status !== "deleted").map(item => <div>item.name</div>)}
I am working on carousel slider. In which i use 2 arrays. 1 array 'slideData' of objects which contain images and id's and the other array 'slideNum' contain indexs to iterate. slideArr is the final array which we will map, it contain images from 'slideData' and map according to 'slideNum' indexes. When i update 'slideArr' array with useState than is not updating but when i update directly using array.splice than its working.
const SecondGrid = () => {
const len = SlideData.length - 1;
const [first, setFirst] = useState(1);
const [second, setSecond] = useState(2);
const [third, setThird] = useState(3);
let [slideNum, setSlideNum] = useState([0, 1, 2]);
const [slideArr, setSlideArr] = useState([
{
id: 0,
imgsrc: "./assets/c1.jpg",
data: "Here is Light salty chineese touch",
},
{
id: 1,
imgsrc: "./assets/c2.jpg",
data: "Try our special breakfast",
},
{
id: 2,
imgsrc: "./assets/c3.jpg",
data: "Taste the italian likalu food",
},
]);
const next = () => {
setFirst(first >= len ? 0 : (prevState) => prevState + 1);
setSecond(second >= len ? 0 : (prevState) => prevState + 1);
setThird(third >= len ? 0 : (prevState) => prevState + 1);
// const arr = [...slideArr];
// storing next index image into all three Cards
slideNum.forEach((val, key1) => {
SlideData.forEach((value, key) => {
if (key === val) {
slideArr.splice(key1, 1, value);
// this is not working
// arr[key1] = value;
// console.log(arr);
// setSlideArr(arr);
//console.log(slideArr);
}
});
});
};
useEffect(() => {
next();
}, []);
useEffect(() => {
const interval = setTimeout(() => {
//updaing slideNum number,in which 'first' contain id of image which will be on 0 index, its updating through useState.
setSlideNum([first, second, third]);
next();
}, 3000);
return () => clearTimeout(interval);
}, [first, second, third]);
return (
<Container>
<div className="row">
<div className="d-flex flex-wrap justify-content-around ">
{slideArr.map((val) => (
<SlideCard val={val} />
))}
</div>
</div>
</Container>
);
};
It would be helpful to have some standalone code that demonstrates the problem but just looking at the commented out bit you say is not working, you can use map() to change values in an array:
setSlideArr(slideArr.map(item => (item.id === 1 ? value : item)))
Here's a demo CodeSandbox.
Also, your console.log(slideArr) should be in a useEffect hook if you want to see the state value after it has changed.
As far as I know, what happens is that you can't splice slideArr because it's useState, what you would be better off doing is:
if (key === val) {
var arr = slideArr;
arr.splice(key1, 1, value);
setSlideArr(arr);
}
I'm currently trying to use a Drag & Drop Library called dnd-kit and its hook called useSortable.
So far I did achieved to make everything draggable the way I like, unfortunately somehow its not possible for me to update the state accordingly to the dragging.
Attached you can find also an example of my code.
I'm glad for every suggestion to solve my problem :)
import React, { useState } from "react";
import "./styles.css";
import { DndContext } from "#dnd-kit/core";
import { arrayMove, SortableContext, useSortable } from "#dnd-kit/sortable";
import { CSS } from "#dnd-kit/utilities";
function SortableItem({ item }) {
const { id, name } = item;
const {
attributes,
listeners,
setNodeRef,
transform,
transition
} = useSortable({ id: id });
const style = {
transform: CSS.Transform.toString(transform),
transition
};
return (
<li ref={setNodeRef} style={style} {...attributes} {...listeners} draggable>
Movement for {name}
</li>
);
}
export default function App() {
const [items, setItems] = useState([
{ id: 1, name: "Items One" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Items 3" }
]);
const handleDragEnd = (event) => {
console.log(event);
const { active, over } = event;
if (active.id !== over.id) {
setItems((items) => {
console.log(items);
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
const newItemsArray = arrayMove(items, oldIndex, newIndex);
return newItemsArray;
});
}
};
console.log(items);
return (
<div className="App">
<h1>Sorting Example</h1>
<DndContext onDragEnd={handleDragEnd}>
<SortableContext items={items}>
{items.map((item) => (
<SortableItem key={item.id} item={item} />
))}
</SortableContext>
</DndContext>
</div>
);
}
You're main issue is in these lines:
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
Since you're using an array of objects and not just an array, you'll need to find the index through find as well and not just indexOf to get its index. Like so:
const oldItem = items.find({ id }) => id === active.id)
const newItem = items.find({ id }) => id === over.id)
const oldIndex = items.indexOf(oldItem);
const newIndex = items.indexOf(newItem);
const newItemsArray = arrayMove(items, oldIndex, newIndex);
If you're using typescript, any linting can complain that oldItem or newItem can be undefined and indexOf doesn't accept undefineds, so you can just use ! to force it because you know that the item will exist (usually), e.g.: const oldIndex = items.indexOf(oldItem!);
Update: or even better/cleaner using findIndex as suggested by #Ashiq Dey:
const oldIndex = items.findIndex({ id }) => id === active.id)
const newIndex = items.findIndex({ id }) => id === over.id)
const newItemsArray = arrayMove(items, oldIndex, newIndex);
The problem is with how you are trying to match id in indexOf method. if your items array looks like this
[
{id:1,name:"Item 1"},
{id:3,name:"Item 2"},
{id:4,name:"Item 3"}
]
then you should use the below code to make the match for respective id f.id === active.id but you were just passing the value of id and not making a conditional match.
function handleDragEnd(event) {
const { active, over } = event;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.findIndex(f => f.id === active.id);
const newIndex = items.findIndex(f => f.id === over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
}
I'm trying to make a shopping app which repeats the same card. Instead of manually rendering them, I used map to render array objects like this:
Parent component:
const Home = () => {
const dummyData = [
{
id: 1,
title: tshirt,
price: 10
},
{
id: 2,
title: hat,
price: 20
}
]
const [totalPrice, setTotalPrice] = useState(0);
const itemNo = 2;
const handleClick = (price) => {
setTotalPrice(price * itemNo);
}
const RenderCards = () => {
return (
dummyData.map(
(d) =>
<Card key={d.id} title={d.title} price={d.price} totalPrice={totalPrice}/>
)
)
}
return(
<>
<RenderCards />
</>
)
}
and the child component:
const Card = (id, title, price, totalPrice) => {
return (
<>
<div key={id}>
<p>{title}</p>
<p>{totalPrice}</p>
<button onClick={() => handleClick(price)}>Click for total price</button>
</div>
</>
)
}
When clicking the button on each card, I'd like to display total price for each card, i.e. for card 1, total price should be 10 * 2 = 20, and for card 2 should be 40. Clicking card 1 should only change {totalPrice} of card 1, card 2 should not be affected, vice versa.
However what I have so far is when clicking the button, both card would show the same total price. I understand this behaviour as the same data is passed to the card component, but how can I individually set data for each card in this case when components are rendered from array map?
const Home = () => {
const dummyData = [
{
id: 1,
title: tshirt,
price: 10
},
{
id: 2,
title: hat,
price: 20
}
]
const [totalPrice, setTotalPrice] = useState([]); //<-- an empty array for start
const handleClick = (id, qty) => {
let newState = [...totalPrice]; //<--- copy the state
if (newState.find(item => item.id === id) != undefined) { // find the item to add qty, if not exists, add one
newState.find(item => item.id === id).qty += qty
} else {
newState.push({id:id, qty:qty});
}
setTotalPrice(newState); //<-- set the new state
}
const RenderCards = () => {
return (
dummyData.map(
(d) => {
const stateItem = totalPrice.find(item=> item.id === d.id); // return the item or undefined
const qty = stateItem ? stateItem.qty : 0 // retreive qty from state by id or 0 if the product is not in the array
return (
<Card key={d.id} title={d.title} price={d.price} totalPrice={d.price * qty}/> //calculate the total
)
}
)
)
}
return(
<>
<RenderCards />
</>
)
}
and card:
const Card = (id, title, price, totalPrice) => {
return (
<>
<div>
<p>{title}</p>
<p>{totalPrice}</p>
<button onClick={() => handleClick(id, 1)}>Click for add one</button> // add one, total price will be good on the next render
</div>
</>
)
}
maybe buggy but the idea is here
Array not getting cleared to null or empty in setState on click in react.
When I click on the submit button, the array must be set to []. It is setting to [], but on change the previous array of items comes into the array.
let questions = [];
let qns = [];
class App extends Component {
constructor(props) {
super(props);
this.state = {
btnDisabled: true,
//questions: [],
};
}
changeRadioHandler = (event, j) => {
this.setState({ checked: true });
const qn = event.target.name;
const id = event.target.value;
let idVal = this.props.dat.mat.opts;
let text = this.props.dat.mat.opt;
let userAnswer = [];
for (let j = 0; j < text.length; j++) {
userAnswer.push(false);
}
const option = text.map((t, index) => ({
text: t.text,
userAnswer: userAnswer[index],
}));
const elIndex = option.findIndex((element) => element.text === id);
const options = { ...option };
options[elIndex] = {
...options[elIndex],
userAnswer: true,
};
const question = {
options,
id: event.target.value,
qn,
};
questions[j] = options;
qns = questions.filter((e) => {
return e != null;
});
console.log(qns, qns.length);
this.setState({ qns });
if (qns.length === idVal.length) {
this.setState({
btnDisabled: false,
});
}
};
submitHandler = () => {
console.log(this.state.qns, this.state.questions);
this.setState({ qns: [] }, () =>
console.log(this.state.qns, this.state.questions)
);
};
render() {
return (
<div class="matrix-bd">
{this.props.dat.mat && (
<div class="grid">
{this.props.dat.mat.opts.map((questions, j) => {
return (
<div class="rows" key={j}>
<div class="cell main">{questions.text}</div>
{this.props.dat.mat.opt.map((element, i) => {
return (
<div class="cell" key={i}>
<input
type="radio"
id={j + i}
name={questions.text}
value={element.text}
onChange={(event) =>
this.changeRadioHandler(event, j)
}
></input>
<label htmlFor={j + i}>{element.text}</label>
</div>
);
})}
</div>
);
})}
</div>
)}
<div>
<button
type="button"
class="btn btn-primary"
disabled={this.state.btnDisabled}
onClick={this.submitHandler}
>
SUBMIT
</button>
</div>
</div>
);
}
}
export default App;
On button click submit, the array must be set to [] and when on change, the value must be set to the emptied array with respect to its index.
changeRadioHandler = (event, j) => {
// the better way is use local variable
let questions = [];
let qns = [];
...
}
submitHandler = () => {
console.log(this.state.qns, this.state.questions);
this.setState({ qns: [] }, () =>
console.log(this.state.qns, this.state.questions)
)}
// clear the previous `qns`
// if u use local variable. you don't need those lines
// this.qns = []
// this.questions = []
}
Finally, found out the solution.
After adding componentDidMount and setting questions variable to null solved my issue.
componentDidMount = () => {
questions = [];
};
Thanks all for your efforts and responses!