I know this has been asked before, see for example here, here and here.
However, none of the answers/comments are satisfactory. They either tell you to clone the data which doesn't solve the problem or to set a unique key on the ListView which solves the problem but creates a new one.
When you set a unique key on the ListView and update it after deletion the entire view renders again and scrolls to the top. A user who scrolls down a list to delete an item expects the list to maintain it's position.
Here's a contrived example, using what I would assume was the correct way of cloning the data:
var ListViewExample = React.createClass({
getInitialState() {
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => {
r1 !== r2
}});
var rows = ['row 1', 'row 2', 'row 3'];
return {
dataSource: ds.cloneWithRows(rows),
};
},
_deleteRow() {
var rows = ['row 1', 'row 3'];
this.setState({dataSource: this.state.dataSource.cloneWithRows(rows)})
},
renderRow(rowData, sectionID, rowID) {
return <TouchableOpacity onPress={this._deleteRow}
style={{height: 60, flex: 1, borderBottomWidth: 1}}>
<Text>{rowData}</Text>
</TouchableOpacity>
},
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
);
}
});
And here you can see a gif of it in action where the wrong row get's removed. The alternative, to set the key
prop of the ListView does remove the correct row but as I said will cause the list to scroll to the top.
UPDATE
I've already accepted Nader's answer below but this does not seem to be the right way because it does not call rowHasChanged
on the dataSource. It also doesn't conform to the ListView documentation. It did solve my problem so I'll leave it marked as the answer but I have the feeling this is a workaround rather than the correct solution
ANOTHER UPDATE
Okay this is awkward but my rowHasChanged
function was missing the return
statement. Too much ES6 sugar in my coffee I guess.
So in conclusion, if you screw up your rowHasChanged
function, this._ds
will work for some reason. If you do things the right way to begin with you should probably use something like:
this.setState({
dataSource: this.state.dataSource.cloneWithRows(rows)
});
when updating your dataSource
.
Remember also that you need a new rows
datablob. Mutating it in place with something like cloneWithRows(rows.push(newRow)
will not work whereas cloneWithRows(rows.concat([newRow])
will.
I think the problem is that you are setting your datasource based on the previous ListView.DataSource instance. I've set up a demo of what I am talking about here, and put the example below along with another way of doing this.
Try doing this:
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
TouchableOpacity,
ListView
} = React;
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => { r1 !== r2 }});
var SampleApp = React.createClass({
getInitialState() {
var rows = ['row 1', 'row 2', 'row 3'];
return {
dataSource: ds.cloneWithRows(rows),
};
},
_deleteRow() {
var rows = ['row 1', 'row 3'];
this.setState({dataSource: ds.cloneWithRows(rows)})
},
renderRow(rowData, sectionID, rowID) {
return <TouchableOpacity onPress={this._deleteRow}
style={{height: 60, flex: 1, borderBottomWidth: 1}}>
<Text>{rowData}</Text>
</TouchableOpacity>
},
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
);
}
});
https://rnplay.org/apps/YGBcIA
You can also use rowID in renderRow to identify the item you want to delete, then in your delete function, reset the state of the dataSource.
Check out the example I've set up here. Also, the code is below:
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
TouchableOpacity,
ListView
} = React;
var rows = ['row 1', 'row 2', 'row 3'];
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => {
r1 !== r2
}});
var SampleApp = React.createClass({
getInitialState() {
return {
dataSource: ds.cloneWithRows([]),
rows: rows
};
},
componentDidMount() {
this.setState({
dataSource: ds.cloneWithRows( this.state.rows )
})
},
_deleteRow(rowID) {
this.state.rows.splice(rowID, 1)
this.setState({
dataSource: ds.cloneWithRows( this.state.rows ),
})
},
renderRow(rowData, sectionID, rowID) {
return <TouchableOpacity onPress={ () => this._deleteRow(rowID) }
style={{height: 60, flex: 1, borderBottomWidth: 1}}>
<Text>{rowData}</Text>
</TouchableOpacity>
},
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 28,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
fontSize: 19,
marginBottom: 5,
},
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With