Reputation: 3691
I am rendering an array of strings as text inputs. When rendering, I render one extra input, which updates an index one past the end of the array. The problem is that after the first character has been entered, when the new input is first rendered, the current input loses focus. (Fiddle)
The items have a key prop, including the one extra item past the end of the array. Should I file a bug report, or is there something I am missing here about how form inputs work?
let MiniList = React.createClass({
getInitialState() {
return {
items: []
};
},
update( index, value ) {
let items = this.state.items;
items[ index ] = value;
this.setState({ items: items });
},
renderItem( value, index ) {
return (
<div key={ index }>
{ index }
<input type="text"
value={ value }
onChange={ event => this.update( index, event.target.value )}/>
</div>
);
},
render() {
return (
<div>
{ this.state.items.map( this.renderItem )}
{ this.renderItem( null, this.state.items.length )}
</div>
);
}
});
React.render( <MiniList />, document.body );
Upvotes: 1
Views: 2184
Reputation: 36408
<div>
{ this.state.items.map( this.renderItem )}
{ this.renderItem( null, this.state.items.length )}
</div>
compiles to
React.createElement("div", null,
this.state.items.map( this.renderItem ),
this.renderItem( null, this.state.items.length )
)
Where the key of every element in this.state.items.map( this.renderItem )
will be prefixed by the index of the array in the children list (in your case, 0
). This makes reconciliation between any element in this.state.items.map( this.renderItem )
and the element returned by this.renderItem( null, this.state.items.length )
impossible, as they will not share the same key. So when you add a new item to this.state.items
, the last input is unmounted and a new input is mounted in its place, which explains the loss of focus.
To fix this, make sure that all your item elements are in the same array.
<div>
{
this.state.items
.map( this.renderItem )
.concat( [this.renderItem( null, this.state.items.length )] )
}
</div>
or simply
<div>
{ this.state.items.concat([null]).map( this.renderItem ) }
</div>
Upvotes: 3