I have a ponent 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 ponent 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';
// ponents
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 ponent
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 ponent
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>
)
};
I have a ponent 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 ponent 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';
// ponents
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 ponent
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 ponent
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>
)
};
Share
Improve this question
asked Dec 26, 2019 at 17:08
user4747724user4747724
1
-
2
Each of your slides should have a
key
property with a unique ID. It's probably not the source of your problem, but it could be. The key helps React keep track of which elements need re-rendering when a list of elements backed by array data changes. – tobiasfried Commented Dec 26, 2019 at 17:19
3 Answers
Reset to default 3This kind of unexpected behaviour is mon 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>
))
}
As described https://reactjs/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>
)
In my case, I had given all keys. Since I was using a Slick Slider Component. All the ponents 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;
}
}