React: after deleting element from array deleted element is shown - reactjs

Hello I'm having strange issue after deleting an element from array , the same element is shown in the UI but the element is being deleted from array.
I have already seen this : React rendering deleted array element after setState()
this is the gif
this is how I delete the element:
const deleteTaglia = useCallback(
(i) => {
let arraytaglie = variant.taglie;
arraytaglie.splice(i, 1);
setVariant((pv) => {
return { ...pv, taglie: arraytaglie };
});
},
[variant.taglie]
);
this is where I render my array:
{variant.taglie.map((v, i) => (
<Taglia
key={i}
singleTaglia={v}
index={i}
addOrUpdateTaglia={addOrUpdateTaglia}
deleteTaglia={deleteTaglia}
/>
))}
this is my state:
const [variant, setVariant] = useState({
color: "",
codiceArticolo: "",
ean: "",
imagesVariant: [],
taglie: [],
});

Array.splice modifies the array "in place" (see the docs), so when you call setState the change within your array doesn't cause a re-render (because the reference to the array doesn't change). Instead, you need to replace the array itself. There are a few options, but the smallest change from your current code is just to create a new array using the spread operator:
const deleteTaglia = useCallback(
(i) => {
let arraytaglie = variant.taglie;
arraytaglie.splice(i, 1);
setVariant((pv) => {
return { ...pv, taglie: [...arraytaglie] };
});
},
[variant.taglie]
);
In addition, using an index for a key can also create problems when adding/removing items as it isn't clear that the object the you have deleted is no longer in the position it was (as the key is used to detect changes). Change the key property to something unique to each item and your delete will lead to an update as you expect.

Related

Change Boolean value based on the previous state value

I have created the toggle function where it will change the Boolean value to false. And I am passing that handler function to button, now I am trying to achieve the same by using previous value, the problem I am facing here is I am having a mock data which will have the following structure {[{}]} inside one object I'll have an array inside that I'll have another objects. I have posted the mock and older implementation by selecting only one value from the mock, could any one guide me how to change the boolean value for the mock which I have. Thanks in advance.
const custDetail = {
customers: [
{
name: "Abc",
isCreated: true,
},
{
name: "bcd",
isCreated: true,
},
{
name: "Dec",
isCreated: true,
},
],
};
Code:
const [creatingCust, setCreatingCust] = useState([custDetail])
const custData = [...creatingCust]
custData[0].customers[0].isCreated = false
setCreatingCust(custData)
//trying to use prevState but I am getting undefined
const onClick = () => {
setCreatingCust(prevState => ({isCreated:!prevState.customers[0].isCreated}))
Shallow copy the state, and all nested state, that is being updated. I suggest using the customer name property to match the customer element in the customers array that you want to update. Use Array.prototype.map to create a new array reference.
I suggest also just storing custDetail in the creatingCust state. I don't a reason to nest it in an array.
Example:
const [creatingCust, setCreatingCust] = useState(custDetail);
const onClick = (name) => {
setCreatingCust(prevState => ({
...prevState,
customers: prevState.customers.map(
customer => customer.name === name
? {
...customer,
isCreated: !customers.isCreated
}
: customer
),
}));
};
If you must have creatingCust be an array the process is similar, but instead of shallow copying into a new object you shallow copy into a new array.
const onClick = (name) => {
setCreatingCust(prevState => [{
...prevState[0],
customers: prevState[0].customers.map(
customer => customer.name === name
? {
...customer,
isCreated: !customers.isCreated
}
: customer
),
}]);
};

Do I have a stale closure in my React app using react-dnd for drag and drop?

I am building a drag-n-drop application that lets users build up a tree of items that they've dragged onto a list.
When dragging items onto a list, there are two possible actions:
The item is dropped onto the list and added
The item is dropped onto an existing item and is added as a child of that item
There is a bug when adding child items. I have reproduced the bug in CodeSandbox here:
https://codesandbox.io/s/react-dnd-possible-stale-closure-2mpjju?file=/src/index.tsx**
Steps to reproduce the bug:
Open the CodeSandbox link
Drag 3 or more items onto the list
Drag an item and drop it onto the first or second item
You will see it successfully adds the item as a child, but the root items below it get removed
I will explain what I think is happening below, but here's an overview of the code:
A list of draggable items:
export function DraggableItems() {
const componentTypes = ["Car", "Truck", "Boat"];
return (
<div>
<h4>These can be dragged:</h4>
{componentTypes.map((x) => (
<DraggableItem itemType={x} />
))}
</div>
);
}
function DraggableItem({ itemType }: DraggableItemProps) {
const [, drag] = useDrag(() => ({
type: "Item",
item: { itemType: itemType }
}));
return <div ref={drag}>{itemType}</div>;
}
...these items can be dropped on two places: the DropPane and the previously DroppedItems.
DropPane:
export function DropPane() {
const [items, setItems] = useState<Array<Item>>([]);
const [, drop] = useDrop(
() => ({
accept: "Item",
drop: (droppedObj: any, monitor: any) => {
if (monitor.didDrop()) {
console.log("Drop already processed by nested dropzone");
} else {
const newItem = makeRandomItem(droppedObj.itemType);
setItems([...items, newItem]);
}
}
}),
[items]
);
const deleteItem = (idToDelete: number) => {
// a recursive function which filters out the item with idToDelete
const deleteItemRecursively = (list: Array<Item>) => {
const filteredList = list.filter((x) => x.id !== idToDelete);
if (filteredList.length < list.length) {
return filteredList;
} else {
return list.map((x) => {
x.children = deleteItemRecursively(x.children);
return x;
});
}
};
// the recursive function is called initially with the items state object
const listWithTargetDeleted = deleteItemRecursively(items);
setItems(listWithTargetDeleted);
};
const addItemAsChild = (child: Item, targetParent: Item) => {
// same as the delete function, this recursive function finds the correct
// parent and adds the child item to its list of children
const addItemAsChildRecursively = (list: Array<Item>) => {
return list.map((x) => {
if (x.id === targetParent.id) {
x.children.push(child);
return x;
} else {
x.children = addItemAsChildRecursively(x.children);
return x;
}
});
};
// it's called initially with the items state object
const reportComponentsWithChildAdded = addItemAsChildRecursively(items);
setItems(reportComponentsWithChildAdded);
};
return (
<div ref={drop} style={{ border: "1px solid black", marginTop: "2em" }}>
<h4>You can any items anywhere here to add them to the list:</h4>
{items.length === 0 || (
<DroppedItemList
items={items}
onDeleteClicked={deleteItem}
addItemAsChild={addItemAsChild}
indentation={0}
/>
)}
</div>
);
}
It's the addItemAsChild function that I believe may be causing the error, but I am not sure since if there is a stale closure - i.e. the items list is getting wrapped and passed into the DroppedItemList to be called - I would think it would happen for the deleteItem function, but that method works fine.
To elaborate, if I add 5 items to the list, then add a breakpoint in addItemsAsChild and drop an item on the #1 in the list (to add it as a child), the items state object only has one item in it (it should have 5 since there are 5 items on screen). If I drop an item onto the 2nd item in the list, the item state object has 2 items in it instead of 5... and so on. It seems that the item state gets closed within addItemsAsChild when that item is rendered, but this is only happening for addItemsAsChild and not for the delete?
I cannot figure out why this is happening and several fixes have failed. Can anyone help? Alternative approaches are welcome if you think I'm doing something wrong.
Just figured this out after many wasted hours. react-dnd really need to improve their documentation as this is not an adequate explanation of what the useDrop() hook needs:
depsA dependency array used for memoization. This behaves like the built-in useMemoReact hook. The default value is an empty array for function spec, and an array containing the spec for an object spec.
The translation is that any state objects that will be modified by any callback within useDrop needs to be referenced in the dependency array.
In my DropPane I have a list of components and they appear in the dep array for the useDrop at that level, but in the DroppedItem I have another useDrop.
The solution is the prop-drill the items array all the way down to the DroppedItem component and add the items array as a dependency. I will update the CodeSandbox just for future reference.

mistakes in filter in React state

I have got an initial state in reducer. I get from api products and I should check by key, if product was not repeated, I put it to the state. If was repeated - do nothing. Mistake is that filter does not work and products repeat.
const defaultState = {
productsInMyList: [],
};
//get prodouct
const { productsInMyList } = action.payload;
//check, if it is in state or not
const sameProductsInMyList =
!!state.productsInMyList.includes(
productsInMyList.key
);
const newProductsInMyList = sameProductsInMyList
? state.productsInMyList
: [...state.productsInMyList, productsInMyList];
return {
...state,
productsInMyList: newProductsInMyList,
};
I suspect, based on a comment of yours, that state.productsInMyList is an array of objects and that productsInMyList is also an object. Array.prototype.includes only works with primitives and shallow object equality (i.e. reference equality).
If you are comparing objects in an array and want to know if an array contains some element matching a criteria then you will want to use Array.prototype.some, i.e. does there exist some element in this array meeting this condition.
const sameProductsInMyList = state.productsInMyList.some(
product => product.key === productsInMyList.key
);
I changed includes into find. Thanks all for the help.
const sameProductInMyList = !!state.productsInMyList.find(
(item) => item.key === productsInMyList.key
);

how to render array in object in array? (react)

const checked = [{
food:['apple', 'banana']
drink:['wine', 'beer']
}];
render (
<>
{checked.map((value) => {
value.food.forEach((each) => (
<div>{each}</div>
)
)}
</>
)
I tried this way and noting is shown in browser..
what would be the best way to approach?
Need to Return Your data like below!!
import React from "react";
export default function App() {
let checked = [{
food:['apple', 'banana'],
drink:['wine', 'beer']
}];
return (
<div className="App">
{
checked.map((item) => {
return item.food.map((fruit)=>{
return <h1>{fruit}</h1>
})
})
}
</div>
);
}
Your code has multiple errors.
It should be render instead of rander
While defining object, multiple properties should be separated using a comma. So put comma after the food array.
forEach doesn't return any thing. It just iterates over an array. So, if you want to return something (in this case a div element), use map.
Also, you should use key for each div element otherwise react would give you a warning in the console. This is done so that while re-rendering, based on the keys, react would understand which component to re-render and which to skip. Otherwise all the div would be re-rendered which is a costly operation.
const checked = [
{
food: ["apple", "banana"],
drink: ["wine", "beer"]
}
]
return (
<>
{checked.map((value) => {
return value.food.map((each, index) => {
return <div key={index}>{each}</div>;
});
})}
</>
);
There is a couple of improvements that require to be implemented to make the list displayed.
First, the map method does not return anything.
Two solutions:
Remove the curly brackets checked.map((value) => value...
Add a return keyword: checked.map((value) => { return value...}
The other issue is that the second loop is iterated using the forEach method.
The difference between the two (forEach and map) from MDN:
The forEach() method executes a provided function once for each array
element.
MDN
The map() method creates a new array populated with the results of
calling a provided function on every element in the calling array.
MDN
Basically, it means that forEach does not return anything and that why you need to use map
checked.map((value) => {
return value.food.map((each) => (<div>{each}</div>))
})}
or
checked.map((value) =>
value.food.map((each) => (<div>{each}</div>))
)}
You are iterating over the checked array items using forEach which won't induce any results since the forEach method
executes a provided function once for each array element.
which won't result in a transformed array.
What you are looking for is the map method which
creates a new array populated with the results of calling a provided function on every element in the calling array.
hence returning your transformed items so that they can be rendered (transformed at compilation time to ReactElement using the JSX syntax).
Note that you need to use an HTML tag instead of a React.Fragment the empty tag <> syntax:
const checked = [{
food:['apple', 'banana'], // there is a missing comma here
drink:['wine', 'beer']
}];
render ( // render and not rander
<div> // div instead of empty tag
{checked.map((item) => item.food.map((each) => <div>{each}</div>))}
</div>
)
Can check this approach. if you want to print just food values, below code should work. If you want to print all the values (both food and drink), then uncomment the commented code below.
export default function App() {
const checked = [
{
food: ["apple", "banana"],
drink: ["wine", "beer"]
},
{
food: ["grapes", "oranges"],
drink: ["coke", "sprite"]
}
];
// to display values of all arrays.
// return (
// <>
// {checked.map((value) => {
// const keys = Object.keys(value);
// return keys.map((eachKey) => {
// return value[eachKey].map((individualValue) =>
// (
// <div>{individualValue}</div>
// )
// )
// });
// })
// }
// </>
// );
// To display just food values.
return (
<>
{checked.map((value) => {
return value.food.map((each) => <div>{each}</div>);
})}
</>
);
}

How to add more elements in ReactJS state array?

this.state.counter.map is not a function I need to add object and map to counter
and I need to create and push to counter array and map to counter and show in this browser how to do that?
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counter: [
{value:0, id:1},
{value:0, id:2},
{value:0, id:3},
{value:0, id:4},
]
};
// function for create new array and map to him for delete one of counter in browser
DeleteButten = counterId => {
const counter = this.state.counter.filter(c =>c.id !== counterId);
this.setState({counter});
};
// this function for push new object to array and show in browser
AddCounter = () => {
const counter = this.state.counter.push({value:0 ,id:5});
console.log(this.state.counter);
this.setState({counter}); // error this.state.counter.map is not a function i need help to add object and map to counter
};
render() {
return (
<div>
{this.state.counter.map(counter => (
<Counter
key={counter.id}
onDelete{this.DeleteButten}
value={counter.value}
id={counter.id} selected={true}
/>
)}
<button onClick={this.AddCounter} className='btn btn-outline-info btn-sm m-2'> ADD result</button>
</div>
);
}
}
export default Counters;
In your code this line:
const counter = this.state.counter.push({value:0 ,id:5});
You're mutating the state array directly, which is not allowed in React.
You can either (shallow) clone the array and modify it:
// This does a shallow clone of the array.
const newCounter = [...this.state.counter];
// Modify the new array.
newCounter.push({ value: 0, id: 5 });
// Set the new array back to the state using setState.
this.setState({ counter: newCounter });
Or, you can use the shorter syntax for just appending new elements and making a new array at the same time:
// Append a new element to the new cloned array.
this.setState({ counter: [...this.state.counter, { value: 0, id: 5 });
// Or you can put the element at the front if you like :)
this.setState({ counter: [{ value: 0, id: 5 }, ...this.state.counter]);
The triple dot ([...array]) syntax is called spread, and can be used with both arrays and objects to conveniently clone or reconstruct new array or objects! Check out the MDN docs about it here.
One more little thing to improve is the above code works mostly, but note that React setState is async, there's a chance for race condition
Say for example you call your AddCounter method multiple times within the same event loop, the later results may override the previous ones.
Therefore, if you are setting something that depends on the current state, it's recommended to use the callback syntax:
this.setState(state => { counter: [...state.counter, { value: 0, id: 5 }] });
See more details and examples of the async nature of setState in React docs.
If you just want to add element to array here is the code :
this.setState({counter:...this.state.counter,new_count})
new_count is what you are trying to push

Resources