Reputation: 339
I am working on a react native app where I am trying to make some images act like a button so that when you press on them they print a statement in the console.
The images are displayed like this:
The code I have is:
class ImageList extends Component {
componentWillMount(){
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(this.props.images);
}
imageTouched(){
console.log('pressed');
}
renderRow(rowData){
const {uri} = rowData;
return(
<View style={{padding: 1, alignSelf: 'flex-start'}}>
<TouchableHighlight onPress={this.imageTouched}>
<Image style={styles.imageSize} source={{uri: uri}} />
</TouchableHighlight>
</View>
)
}
render() {
return (
<View>
<ListView
contentContainerStyle={styles.list}
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
</View>
);
}
}
var styles = StyleSheet.create({
imageSize: {
//newWidth is the width of the device divided by 4.
//This is so that four images will display in each row.
width: newWidth,
height: newWidth,
padding: 2
},
list: {
flexDirection: 'row',
flexWrap: 'wrap'
}
});
When I run it there are no errors but when I touch the images nothing happens. I have checked the console but nothing is printed.
How do I get each image to act as a button?
Upvotes: 0
Views: 2551
Reputation: 992
Like others have mentioned, the problem is that this
is not bound in the renderRow()
-method. I think the easiest way to fix this is to change renderRow()
to be an arrow-function:
renderRow = (rowData) => {
const {uri} = rowData;
return (
<View style={{padding: 1, alignSelf: 'flex-start'}}>
<TouchableHighlight onPress={this.imageTouched}>
<Image style={styles.imageSize} source={{uri: uri}} />
</TouchableHighlight>
</View>
);
}
Arrow function always have this
set to their containing scope when invoked, so now this.imageTouched
will resolve.
Notice that you don't have to do anything with your imageTouched()
-function or invocation, since it's not referencing this
.
PS. This syntax depends on Public Class Fields, which is a Stage 2 proposal of the language standard at the time of writing (likely to be included, already in use in internal React-code). This feature is possible to use with a babel-plugin that is enabled by default in React Native projects.
PS2. Note that declaring the method with an arrow function instead of using an arrow function in the invocation will create one instance of the method per component instance, instead of one instance per render. This should really be fine performance-wise.
Upvotes: 2
Reputation: 11244
Try this
<TouchableHighlight onPress={() => this.imageTouched()}>
Make sure to bind renderRow
renderRow={this.renderRow.bind(this)}
Upvotes: 0
Reputation: 35970
React components defined as ES6 classes do not autobind methods' this
context to the component instance. In this particular case, the renderRow
callback is not bound and the context refers to the global application context instead, so the reference to this.imageTouched
is undefined.
A common pattern you see a lot is to bind the callbacks in your render method:
<ListView
renderRow={this.renderRow.bind(this)}
/>
This, however, has the effect of creating a new instance of the function on every render, which causes unnecessary work for the garbage collector.
Another alternative is to use (lexically scoped) arrow functions and call the methods explicitly:
<ListView
renderRow={(data) => this.renderRow(data);
/>
But this has the same unwanted effect of unnecessarily creating functions on every render.
A slightly more verbose, but more "correct" way is to bind the callbacks in the class constructor:
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
}
Upvotes: 0