Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access `this` inside a renderRow of a ListView?

I'm having trouble to run a function for the onPress event inside a row of a ListView. I'm following the React Native tutorial trying to continue from there. It seems it's using ES6 syntax style.

This is the relevant part of the code.

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */

import React, {  
  TouchableHighlight,
  AppRegistry,
  Component,
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
  Alert,
} from 'react-native';

class AwesomeProject extends Component {
  constructor(props) {
    super(props);
    this.something = this.something.bind(this); // <-- Trying this, not even sure why
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  }

//
//Irrelevant code here. Fetching stuff and renderLoadingView
//

  something = function(){
    console.log('something');
    Alert.alert(
      'Alert Title',
      'alertMessage',
    );
  }


  render() {
    console.log('this', this); //this is an instance of AwesomeProject
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderMovie}
        style={styles.listView}
        />
    );
  }

  renderMovie(movie) {
    console.log('Not this', this); //this is not an instance of AwesomeProject
    return (
      <TouchableHighlight onPress={() => {console.log(this); this.something()}}>
     <View style={styles.container}>
        <Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}                    
        />
        <View style={styles.rightContainer}>
          <Text style={styles.title}

          >{movie.title}</Text>
          <Text style={styles.year}>{movie.year}</Text>
        </View>
      </View>
       </TouchableHighlight>
    );
  }
}

//
//More irrelevant code here. Styles and the 
//

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

I'm unable to run the something function. I've tried various syntax without success. The problem seems to be that this is not what I expect it to be, and it doesn't have the variable something defined.

The only way I had to make it work was to declare an external variable with var something = function(){...} outside the AwesomeProject class.

Is there a way to access something while being declared inside AwesomeProject? Is this maybe a bad practice according to flux architecture or something?

like image 757
xabitrigo Avatar asked Apr 21 '16 18:04

xabitrigo


2 Answers

Turns out I found the solution myself, here: https://github.com/goatslacker/alt/issues/283#issuecomment-115391700

The problem is that with ES6 syntax you have no autobinding of this, so you need to do it yourself.

I've learned a bit more since I posted this answer. The real problem is that this is "bound" when the function is executed, not when it's declared. It points to the object the function is attached to when called. For example:

obj.foo(); //inside foo, this points to obj
bar(); //inside bar, this points to the global object (or undefined in strict mode)

In my example the function this.renderMovie() is passed as a parameter. So inside ListView it will be a "local variable". It will be called "detached" from any object, like the bar() example before, so this will not be what I expect.

It's possible to force the binding of this.

There are 3 possible solutions to my problem, and I like the 3rd one the best:

Option 1: Manually bind this on call

On the code, replace the referece to {this.renderMovie} whith {this.renderMovie.bind(this)}

  render() {
    console.log('this', this); //this is an instance of AwesomeProject
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderMovie.bind(this)} //<-- change here
        style={styles.listView}
        />
    );
  }

Option 2: Do the binding on the constructor

And of course, bind the right function:

  constructor(props) {
    super(props);
    this.something = this.something.bind(this); // <-- Not this function
    this.renderMovie = this.renderMovie.bind(this); // <-- This function!!
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  }

Option 3: use the arrow syntax and let them do the job

I like this one the best. You just need to declare the involved function using the arrow syntax so this:

  renderMovie(movie) {
    //code here
  }

becomes this:

  renderMovie = (movie) => {
    //code here
  }
like image 98
xabitrigo Avatar answered Oct 02 '22 07:10

xabitrigo


In addition to https://stackoverflow.com/a/36779517/6100821

Option 4: use bind operator

<ListView
    dataSource={this.state.dataSource}
    renderRow={::this.renderMovie} //<-- cleaner, than bind
    style={styles.listView}
    />

Be careful, it's just a proposal, not a standard!

like image 36
Роман Парадеев Avatar answered Oct 02 '22 07:10

Роман Парадеев