Reputation:
I have a component which renders a file list. The methods on it are simple add, update, and remove. I'm experiencing behavior that screams closure problem but I can't figure out what. The component stores the list of files in state as an array. So when rendering I just map over them. Updating items works as you'd expect it to so I believe the correct ids are being passed for that method, but removal always passes the id of the last item mapped over.
So if I have 3 items added: 1. file #1 2. file #2 3. file #3
and I click to update #2 - all works as expected file #2 is set to whatever file I placed in there and state reflects the correct list.
but if I try to remove file #1 or #2 file #3 gets removed.
What am I missing?
// npm modules
import React, { useState } from 'react';
import randomstring from 'randomstring';
// components
import { PrimaryButton, InlineButton } from "../buttons/buttons";
import { AdminInput } from "../inputs/textInputs";
export const AdminSlides = ({ setSlides }) => {
const [ state, updateState ] = useState({
slides: [],
});
function setState(value){
updateState( prevState => ({
...prevState,
...value
}));
}
function addSlide(){
const slides = state.slides;
const id = randomstring.generate({ length: 5, charset: 'alphabetic' })
slides.push({ id, slide: null });
setState({ slides });
// send current state to parent component
if (setSlides) setSlides(state.slides);
}
function removeSlide(id){
const slides = state.slides.filter(item => item.id !== id);
setState({ slides });
}
function setSlide(file, id){
const slides = state.slides.map(item => {
if (item.id === id) item.slide = file;
return item;
});
setState({ slides });
// send current state to parent component
if (setSlides) setSlides(state.slides);
}
return (
<div>
{
state.slides.map(slide => (
<div className='m-b:1'>
<AdminInput
type='file'
onChange={e => setSlide(e.target.files[0], slide.id)}
className='m-b:.5'
/>
<InlineButton className='m:0' onClick={()=>removeSlide(slide.id)}>Remove</InlineButton>
</div>
))
}
<PrimaryButton onClick={addSlide}>Add Slide</PrimaryButton>
</div>
)
};
Upvotes: 6
Views: 7517
Reputation: 679
In my case, I had given all keys. Since I was using a Slick Slider Component. All the components were rendered on each other (one below the other). For the active Slider, increasing the z-index and position: relative fixed the above problem.
.slick-active {
.see-more{
z-index: 10;
position: relative;
}
}
Upvotes: 0
Reputation: 2968
As described https://reactjs.org/docs/lists-and-keys.html,
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:
so your problem will be solved if you add key property to div element inside map function.
return (
<div>
{
state.slides.map((slide, index) => (
<div key={slide.id} className='m-b:1'>
<input
type='file'
onChange={e => setSlide(e.target.files[0], slide.id)}
className='m-b:.5'
/>
<button className='m:0' onClick={()=>removeSlide(slide.id)}>Remove</button>
</div>
))
}
<button onClick={addSlide}>Add Slide</button>
</div>
)
Upvotes: 0
Reputation: 4859
This kind of unexpected behaviour is common when you don't use keys or use keys as index.
Your problem will be solved just by using slide.id
as key.
{
state.slides.map(slide => (
<div className='m-b:1' key={slide.id}>
<AdminInput
type='file'
onChange={e => setSlide(e.target.files[0], slide.id)}
className='m-b:.5'
/>
<InlineButton className='m:0' onClick={()=>removeSlide(slide.id)}>Remove</InlineButton>
</div>
))
}
Upvotes: 3