4

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);
Peter G.
  • 7,816
  • 20
  • 80
  • 154

3 Answers3

3

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

Irfan Ayaz
  • 797
  • 8
  • 16
  • You forgot to fix this line inside `componentDidMount`: `fields: this.state.fields.cloneWithRows(this.state.data)` – Peter G. Jul 08 '16 at 07:42
  • I tried the solution, but for some reason I can't trigger the `renderField()` function on update of `data`. `handleClick()` works properly and `data` gets updated. – Peter G. Jul 08 '16 at 07:45
  • Okay, I got so far that it works. My application had this line: `if (!this.state.loaded) { return this.renderLoadingView(); }` for postponing view rendering until data gets loaded that for some reason blocked the `renderField()` call. – Peter G. Jul 08 '16 at 09:09
1

Already answered many times, if you want to changes the row rendering on a props, this props needs to go in your data if you want to row to update.

See these 2 answers:

Community
  • 1
  • 1
gre
  • 1,841
  • 2
  • 16
  • 26
0

Here would be my solution to the question, but it still takes too long to process every click:

handleClick(field) {
  newFields = this.state.fields.map( (x) => x.key === field.key && x.active !== true ? {...x,active:true} : {...x,active:false})  
  this.setState({
    fields:newFields,
    dataSource: this.state.dataSource.cloneWithRows(newFields)
  });
}
Peter G.
  • 7,816
  • 20
  • 80
  • 154