Reputation: 433
Codesandbox link https://codesandbox.io/s/mjxrz2npzy
I am creating an app where users can click on an Add Timer button to create a limited number of custom countdown/countup timers, defined in a component CustomTimerRow. Each row has an icon that if they click it, it will remove the timer.
I have the following parent component containing the list of rows:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import CustomTimerRow from './CustomTimerRow';
import AddRow from './AddRow';
const mapStateToProps = state => {
return {customTimers:state.clock.customTimers}
};
class ConnectedCustomTimers extends Component
{
constructor(props)
{
super(props);
this.buildCustomTimers = this.buildCustomTimers.bind(this);
}
buildCustomTimers()
{
let timerList = [];
let index = 0;
if(this.props.customTimers)
{
for(let t of this.props.customTimers)
{
timerList.push(<CustomTimerRow number={index++} timer={t}/>)
}
}
return timerList;
}
render()
{
const elems = this.buildCustomTimers();
const addRow = (this.props.customTimers.length < 8 ? <AddRow/>:<span/>);
return (<div>
{elems}
{addRow}
</div>
);
}
}
const CustomTimers = connect(mapStateToProps, null)(ConnectedCustomTimers);
export default CustomTimers
My Redux store has the following actions for the timers:
case constants.ADD_CUSTOM_TIMER:
return {...state,customTimers:[...state.customTimers,action.payload]};
case constants.REMOVE_CUSTOM_TIMER:
const newTimers = state.customTimers.slice();
newTimers.splice(action.payload, 1);
return {...state,customTimers:newTimers};
Everything works perfectly fine when adding rows, however when I remove a row, it always removes the last row from the list, no matter if I select a row in the middle to remove, or if I remove the first row.
When I add logging, all the way through to CustomTimers' render everything looks correct. buildCustomTimers is returning a list of objects that I would expect to see. But once the return happens and React does its thing, I see a different result.
Am I going about adding and removing components wrong here? I'm using both state updates and the containing parent to determine what to display but it seems like re-rendering the updated component list is not working correctly.
UPDATE: I updated the code to have the following:
buildCustomTimers()
{
let timerList = [];
let index = 0;
if(this.props.customTimers)
{
for(let t of this.props.customTimers)
{
timerList.push(<li key={index++}><CustomTimerRow key={index++} timer={t}/></li>)
}
}
return timerList;
}
render()
{
const elems = this.buildCustomTimers();
const addRow = (this.props.customTimers.length < 8 ? <AddRow/>:<span/>);
return (<div><ul>
{elems}
</ul>{addRow}</div>
);
}
But that did not change the behavior at all so I do not beleive the List and Key answer may be the correct answer (though not discounting if there is another way to do it)
Upvotes: 2
Views: 5436
Reputation: 169
For me, the solution was that not update tihs.setState(...) with reomved class object, only class parennt.removeChild(...). Otherwise only js object removed and ul/li item remains.
Upvotes: 0
Reputation: 91
Maybe the error is in your CustomTimerRow code but when you create a list of components in a loop you must add a "key" props. This key has to be unique.
If you try:
<CustomTimerRow number={index++} timer={t} key={t.eventType} />
You'll see that it's working. As t.eventType is not unique, this cannot be your definitive key, but this shows you how keys are important.
Upvotes: 5