Search code examples
listviewreactjsonclickreact-nativereact-native-listview

React Native ListView not re-rendering on state change by click


Here is my sample code, I would like the ListView elements to be updated on click.

This line defines the style:

(this.state.selectedField.id==field.id)?'green':'white'

In this example the active View should be highlighted with green color. The state is getting updated inside handleClick(), but renderField() method is not being called.

How to make ListView re-render on state change triggered by click?

RNPlayNative Link

import React, {Component} from 'react';
import {
  AppRegistry,
  View,
  ListView,
  Text,
  TouchableOpacity
} from 'react-native';

class SampleApp extends Component {

  constructor(props) {
    super(props);
    var ds = new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
    });
    this.state = {
      fields: ds.cloneWithRows([
        {id:0},{id:1},{id:2}
      ]),
      selectedField: {id:0}
    };
  }

  handleClick(field) {
    console.log("Selected field:",field);
    this.setState({
      selectedField: field
    });
  }

  renderField(field) {
    return (
      <TouchableOpacity onPress={this.handleClick.bind(this, field)} >
        <View style={{backgroundColor:(this.state.selectedField.id==field.id)?'green':'white'}}>
          <Text style={{left:0, right:0, paddingVertical:50,borderWidth:1}}>{field.id}</Text>
        </View>
      </TouchableOpacity>
    );
  }

  render() {
    return (
      <View>
        <ListView
          dataSource={this.state.fields}
          renderRow={this.renderField.bind(this)}
        />
      </View>
    );
  }
}

AppRegistry.registerComponent('SampleApp', () => SampleApp);

Solution

  • The list is rendered again when the dataSource changes, and in your example since the dataSource never changes, the listview is never re-rendered. This can be achieved by adding a field in state variable to hold the data for dataSource. And in componentDidMount method, have the dataSource clone rows with data state variable. Whenever you'd want to re-render the listview, you will have to change the data state variable only and list will be automatically re-rendered. I have changed you data to add a selected state with every object. Here is the updated code.

    class SampleApp extends Component {
    
      constructor(props) {
         super(props);
         var ds = new ListView.DataSource({
             rowHasChanged: (row1, row2) => row1 !== row2,
        });
         var dataVar = [
             {
               id:0,
               selected: true,
             },{
               id:1,
               selected: false,
             },{
               id:2,
               selected: false,
             }
           ];
         this.state = {
           data: dataVar,
           fields: ds,
         };
       }
    
       componentDidMount() {
    
         this.setState({
           fields: this.state.fields.cloneWithRows(dataVar)
         });
       }
    
    
       handleClick(field) {
         console.log(field);
         field.selected = !field.selected;
    
         var dataClone = this.state.data;
         console.log(dataClone);
    
         dataClone[field.id] = field;
    
         this.setState({
           data: dataClone,
         });
       }
    
       renderField(field) {
         let color = (field.selected == true)?'green':'white';
         return (
           <TouchableOpacity onPress={this.handleClick.bind(this, field)} >
             <View style={{backgroundColor:color}}>
               <Text style={{left:0, right:0, paddingVertical:50,borderWidth:1}}>     {field.id}</Text>
             </View>
           </TouchableOpacity>
         );
       }
    
       render() {
         return (
           <View>
             <ListView
               dataSource={this.state.fields}
               renderRow={(field) => this.renderField(field)}
             />
           </View>
         );
       }
     }
    

    Here is a working rnplay sample