I have an array state filled with objects called items:
const [items, setItems] = useState([{name: "", toppings: []}])
However when I try to delete a specified index with this function:
const removeItem = (index) => {
setItems(items.filter((item, i) => i !== index))
areItemsCompleted()
}
It works when I print the elements to the console outside of the function, however the item is not rendering properly. It only removes the last element of the array from the jsx but the items array has the correct values. I read somewhere that react only checks for changes in state shallowly so it does not check the content of the object it is deleting. However, I am unsure as to why it shows up in the console that items have the correct value but the components arent rendering the correct data. (Instead it renders the same data except for the last element in items).
I have tried multiple ways of deleting elements from my array such as
const removeItem = (index) => {
let arr = [...items]
arr.splice(index, 1)
setItems(arr)
areItemsCompleted()
}
How would I delete an element from my array full of objects and render the proper data?
So I was using the array index as the key prop of the component, but that doesn't work when deleting an item in the array.
React doesn't know which key to delete, as the array index will still exist even after that particular element has been deleted.
Solution
Use a unique key for each element in the array. Something like id which react can see has been deleted.
It's hard to guess;
Check, may be you are re-setting items somewhere later;
Plus functional form is safer:
setItems(lastItems=>lastItems.filter((item, i) => i !== index))
Related
I have the following line that targets an object located in an array of objects, the end point of the array is subjected to changes (dispatch actions) the issue is when a change made the whole component re-renders this issue is present when .... there is an array but, if am accessing a part of an object directly rather than filtering an object from an array of object then this won't happen an workout around this ?
const mainGroups = useAppSelector((state) => state.global.weeks[clientId].filter((week) => week.weekId === currentWeekAtHand)[0].mainGroups[id]);
I have the following method compares two array, one coming from the props and the other one from my own component. Every element that exists in my props array but doesnt exist in my components array is inserted in a third array with the added property called "destroy : true" so i can send it to the back end to be deleted from the database.
However for whatever reason my props is being updated instead of the variables i use in the method to do all this. i am not really sure why since i am not referencing the prop directly but i do copy its content to the variables in the method.
updateArray(){
let updatedArray = []
let oldArray = [...this.props.array]
oldArray.forEach(element => {
if(this.componentArray.indexOf(element) > -1){
updatedArray.push(element)
}else{
let newElement = element
newElement.destroy = true
updatedArray.push(newElement)
}
})
return updatedArray
},
why does this happen exactly? every other element in my component works fine except this.
Yes, you are copying the elements of the this.props.array array into a new array local to the method but given that the elements of the array are objects, both arrays are in the end containing same objects (references to the objects)
You can create shallow copy of the original element with the spread operator let newElement = { ...element } - this creates the completely new object and copy all properties of the original object. But be aware that if any property of original objects contains array/object, you have the same problem... just one level down
I have a map function which renders a list of divs and these divs are based on the components state, so it is dynamic. I need to put a ref on each of these items on the list so that the user can basically press up and press down and traverse the list starting from the top.
Something like this:
const func = () => {
const item = items.map((i, index) => {
return (
<div ref={index}>{i.name}</div>
)
});
}
This has been difficult since it seems like refs are not like state where they can change based on dynamic data. Its initialized once and set to a constant. But I don't know what to initialize it as until this list is rendered and I can't call the useRef hook in a function of course so what is the solution here?
EDIT:
Ok so, its seems there needs to be some more context around this question:
I have a dropdown which also allows the user to search to filter out the list of items. I want to make an enhancement to it so that when the user searches it focuses on the first element and allows the use of arrow keys to traverse the list of elements.
I got stuck when I tried to focus on the first element because the first element doesn't exist until the list is rendered. This list changes dynamically based on what the user types in. So how do you focus on the first element of the list if the list is always changing as the user types?
This can be done without refs, so it should probably be done without refs.
You can put the index of the currently focused element into state. Whenever the displayed data changes, reset the index to 0:
const [focusedElementIndex, setFocusedElementIndex] = useState(0);
useEffect(() => {
setFocusedElementIndex(0);
}, [items]);
// using this approach,
// make sure items only gets a new reference when the length changes
When rendering, if you need to know the focused index to change element style, for example, just compare the index against the state value:
<div class={index === focusedElementIndex ? 'focused' : ''}
And have a keydown listener that increments or decrements focusedElementIndex when the appropriate key is pressed.
In the example here. I have to sliders and I drag them. Because I have two values I store them in useState as an array.
Then storing as an array the ComponentsWithArrayAsProp1 doesn't see changes in the state as it is the same array and does not re-render itself.
In the second example, I store values as values.toString(), but this is not a good solution.
What is a good solution for this case?
Then storing as an array the ComponentsWithArrayAsProp1 doesn't see changes in the state as it is the same array and does not re-render itself.
you are right we can solve it by creating new array everytime as follows,
function onChange1(values) {
setValues1([...values]);
}
but i think there is something wrong with react-slider .because your approach of setting array ,setValues1(values) works when we click on different points of slider1. but it deosn't work when we drag it. there is something else going wrong here ?
Option 1:
As you said, the ComponentsWithArrayAsProp1 doesn't see the changes because the array reference is the same, so it doesn't rerender. But you can save a copy of the array instead of the array itself.
function onChange1(values) {
setValues1(values.slice());
// Another way to do this
// setValues1([...values]);
}
Option 2:
You can also save the array numbers individually, like so:
const [values1Start, setValues1Start] = useState(0);
const [values1End, setValues1End] = useState(0);
function onChange1(values) {
setValues1Start(values[0]);
setValues1End(values[1]);
}
return (
<ComponentsWithArrayAsProp1 values={[values1Start, values1End]} />
);
A common pattern is generating react elements from array. In case of iterating over react components are there any cases where should/can ignore key prop warning?
Like can we ignore in case of element rendering static text etc?
Note: After rendering my elements aren't going to change, add new or remove.
const days = ['Sunday' 'Mon.........,,, 'Saturday'];
days.map(day => <div>{day}</div>);
You shouldn't be ignoring this warning.
Have a read of the doc: https://reactjs.org/docs/lists-and-keys.html#keys
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity
If you truely don't have any key like value to use, you can use the index of the array (assuming its coming from a statically ordered source) so that react can use the keys to identity what has changed.
const myArray = ["Some", "Static", "Stuff"]
const MyComponent = () => (
<div>
{myArray.map((item, i) => <div key={i}>{item}</div>)}
</div>
)