willedanielsson
willedanielsson

Reputation: 1333

React Native: Update view (ListView) on navigator pop

I'm pretty new to React Native and having some problem with understanding the whole ecosystem of it. Anyway, I have a ListView and the user can navigate to another View in order to add a new item which will be added to the list. When I step back (pop) the list is not updated since the datasource is assigned in getInitialState.

How can I force the View to updated the list when I pop? Is this done by handling the route in onDidFocus of the navigator or is there anything I can add to the actual ListView?

Have provided a minified version of the code and it is the Budget-component and its list which needs to be updated when there has been a pop-event from the navigator.

Index-file

var BudgetWatch_ReactNative = React.createClass({

   renderScene(route, navigator){
      if(route.name == 'Main'){
         return React.createElement(route.component, {navigator});
      }
      if(route.name == 'Budgets'){
        return React.createElement(route.component, {navigator, realm});
      }
   },

   onDidFocus(route){
      // Insert React Native magic?
   },
   render() {
      return (
         <Navigator
            ref={(nav) => { navigator = nav; }}
            style={{flex:1}}
            initialRoute={{ name: 'Main', component: Main}}
            renderScene={this.renderScene}
            onDidFocus={this.onDidFocus}
            navigationBar={
               <Navigator.NavigationBar
               routeMapper={NavigationBarRouteMapper(realm)}
               />
         }/>
      )
   }
});

Budget-component

class Budgets extends React.Component{
   render(){
      return (
         <View>
            <BudgetList navigator = {this.props.navigator}/>
         </View>
      )
   }
}

var BudgetList = React.createClass({
   getInitialState: function() {
      var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
      return {
         dataSource: ds.cloneWithRows(this.props.realm.objects('Budget')),
      };
   },

   render: function() {
      return (
         <ListView
            dataSource={this.state.dataSource}
            renderRow={this._renderRow}
         />
      );
   }
};

Hope this is enough for you to understand the problem, thanks!

Edit: Tried to reach a function which will set a new state with data for the list from onDidFocus. But since the call to that function is via route.component.prototype.updateData(data), this.setState returns "Cannot read property 'enqueueSetState' of undefined"

Upvotes: 3

Views: 2425

Answers (2)

I solved this problem using componentDidMount, which is called every time that the Component appear. This solution is currently working for me.

For example,

var ListPage = React.createClass({
  getInitialState() {
    return {
      dataSource: ds.cloneWithRows( this._generateRows( this.props.list ) ),
      currentPosition: 'unknown'
    }
  },

  componentDidMount() {
    this._reloadListView();
  },

  _reloadListView() {
    this.setState({
      dataSource: ds.cloneWithRows( this.props.list ),
    });
  },

  _generateRows( list ) {
    rows = [];
    list.forEach( function( item, index ) {
      rows.push( item );
    });
    return rows
  },

  _renderRow( rowData ) {
    return (
      <Text>{ 'id: ' + rowData[ 'id' ] + ' ' + rowData[ 'info' ][ 'value' ][ 'name' ]}</Text>
    );
  },

  render() {
    return (
      <ListView
        dataSource = { this.state.dataSource }
        renderRow = { this._renderRow }
      />
    )
  },
});

module.exports = ListPage;

Upvotes: -1

willedanielsson
willedanielsson

Reputation: 1333

So I found out that this is supposedly a bug with the rendering of ListView in React Native and by adding some less beautiful code, I got it to work.

I added a method onDidFocus to my index-file which is called everytime a component is shown (this so it can be used when the user pops to the previous view). When the route in onDidFocus was Budget, I return the component again. I also moved out the data which is used in the ListView to my index so ListView gets the data as a property and will re-render when it has changed:

index.android.js

var BudgetWatch_ReactNative = React.createClass({

   renderScene(route, navigator){
      if(route.name == 'Main'){
         return React.createElement(route.component, {navigator});
      }
      if(route.name == 'Budgets'){
          var data = realm.objects('Budget').sorted('name');
          return <Budgets navigator={navigator} realm={realm} data={data} />
      }
   },

   onDidFocus(route){
       if(route.name==='Budgets'){
           var data = realm.objects('Budget').sorted('name');
           return <Budgets navigator={navigator} realm={realm} data={data} />
       }
   },
   render() {
      return (
         <Navigator
            ref={(nav) => { navigator = nav; }}
            style={{flex:1}}
            initialRoute={{ name: 'Main', component: Main}}
            renderScene={this.renderScene}
            onDidFocus={this.onDidFocus}
            navigationBar={
               <Navigator.NavigationBar
               routeMapper={NavigationBarRouteMapper(realm)}
               />
         }/>
      )
   }
});

Next in BudgetComponent.js I just set the data from props as a state and used that state-data to send it as props to my BudgetList-component (I also changed it to a class and not a component but that's not important):

var Budgets = React.createClass({
    getInitialState: function() {
        var propsdata = this.props.data;
        return {
            data: propsdata,
        };
     },

     render(){
       return (
         <View style={styles.container}>
           <BudgetList data={this.props.data} realm={this.props.realm} navigator = {this.props.navigator} />
         </View>
     )
  });

Now this should work but due to the bug, the DataSource in a List does not work correctly so by using a method componentWillUpdate (which is called all the time the List is viewed) I could force the update of data. However, in order to not get stuck in an endless loop, I had to check if the data should be updated or else it would get stuck in updating the data:

BudgetList.js

var BudgetList = React.createClass({
   getInitialState: function() {
      var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
      var data = this.props.data;
      return {
         data: this.props.data,
         dataSource: ds.cloneWithRows(this.props.data),
      };
    },

    componentWillUpdate (nextProps) {
       if (this.state.dataSource._cachedRowCount !== this.props.data.length) {
          this.setState({
          data: this.props.data,
          dataSource: this.state.dataSource.cloneWithRows(this.props.data)
          })
       }
    },

    _renderRow: function(rowData: string, sectionID: number, rowID: number) {
       return(
          <View>
             <Text>{rowData.name}</Text>
          </View>
       )
    }
});

Upvotes: 0

Related Questions