React-Native Updating List View DataSource

Brandon M picture Brandon M · Jul 31, 2015 · Viewed 50.5k times · Source

I have an iOS app I am making with react-native. The Game class contains a ListView component. I set the state in the constructor and include a dataSource. I have a hardcoded array of data for right now that I store in a different state property (this.state.ds). Then in the componentDidMount I use the cloneWithRows method to clone my this.state.ds as my dataSource for the view. That is pretty standard as far as ListViews go and is working well. Here is the code:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

 var React = require("react-native");
 var { StyleSheet, Text, View, ListView, TouchableHighlight } = React;

class Games extends React.Component {
constructor(props) {
    super(props);
    var ds = new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 != r2
    });
    this.state = {
        ds: [
            { AwayTeam: "TeamA", HomeTeam: "TeamB", Selection: "AwayTeam" },
            { AwayTeam: "TeamC", HomeTeam: "TeamD", Selection: "HomeTeam" }
        ],
        dataSource: ds
    };
}

componentDidMount() {
    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(this.state.ds)
    });
}
pressRow(rowData) {
    var newDs = [];
    newDs = this.state.ds;
    newDs[0].Selection = newDs[0] == "AwayTeam" ? "HomeTeam" : "AwayTeam";
    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(newDs)
    });
}

renderRow(rowData) {
    return (
        <TouchableHighlight
            onPress={() => this.pressRow(rowData)}
            underlayColor="#ddd"
        >
            <View style={styles.row}>
                <Text style={{ fontSize: 18 }}>
                    {rowData.AwayTeam} @ {rowData.HomeTeam}{" "}
                </Text>
                <View style={{ flex: 1 }}>
                    <Text style={styles.selectionText}>
                        {rowData[rowData.Selection]}
                    </Text>
                </View>
            </View>
        </TouchableHighlight>
    );
}
render() {
    return (
        <ListView
            dataSource={this.state.dataSource}
            renderRow={this.renderRow.bind(this)}
        />
    );
}
}
var styles = StyleSheet.create({
 row: {
    flex: 1,
    flexDirection: "row",
    padding: 18,
    borderBottomWidth: 1,
    borderColor: "#d7d7d7"
},
selectionText: {
    fontSize: 15,
    paddingTop: 3,
    color: "#b5b5b5",
    textAlign: "right"
}
});


module.exports = Games

The issue I am having comes in the pressRow method. When the user presses the row, I would like the selection to change and for it to render the change on the device. Through some debugging, I have noticed that even though I am changing the Selection property of the object in the newDs array, the same property changes on the object in this.state.ds and similarly changes the object in this.state.dataSource._dataBlob.s1. Through further debugging, I have found that since those other arrays have changed, the ListView's DataSource object doesn't recognize the change because when I set the state and rowHasChanged is called, the array it is cloning matches the array this.state.dataSource._dataBlob.s1 and so it doesn't look like a change and doesn't rerender.

Any ideas?

Answer

eddyjs picture eddyjs · Jul 31, 2015

Try this:

pressRow(rowData){

    var newDs = [];
    newDs = this.state.ds.slice();
    newDs[0].Selection = newDs[0] == "AwayTeam" ? "HomeTeam" : "AwayTeam";
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(newDs)
    })

}

This should make a copy of the array, which can then be modified independently of the original array in this.state.ds.