Here is my confusion:
constructor(props) {
super(props);
}
hookNav(){
console.log("work");
}
renderItem({ item, index }) {
return (
<TouchableOpacity style={{ margin: 9 }} onPress={this.hookNav}>
<View
style={{
flex: 1,
minWidth: 170,
maxWidth: 223,
height: 280,
maxHeight: 280,
borderRadius: 10,
}}
>
<ImageBackground
source={{
uri: "https://picsum.photos/170/223",
}}
style={{ flex: 1 }}
imageStyle={{ borderRadius: 10 }}
>
<Text
style={{
color: "white",
position: "absolute",
bottom: 20,
right: 10,
fontWeight: "bold",
textShadowColor: "black",
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
}}
>
Topic
</Text>
</ImageBackground>
</View>
</TouchableOpacity>
);
}
render(){
return (
<View style={{ marginBottom: 80, backgroundColor: "white" }}>
<Header
centerComponent={{ text: "test", style: { color: "orange" } }}
rightComponent={{ icon: "add", color: "orange" }}
/>
<FlatList
numColumns={2}
onEndReachedThreshold={0}
onEndReached={({ distanceFromEnd }) => {
console.debug("on end reached ", distanceFromEnd);
}}
contentContainerStyle={styles.list}
data={[
{ key: "a" },
{ key: "b" },
{ key: "c" },
{ key: "d" },
{ key: "e" },
{ key: "f" },
]}
renderItem={this.renderItem}
ListHeaderComponent={
<Text
style={{ padding: 10, fontWeight: "bold" }}
>
Your Topics
</Text>
}
/>
</View>
);
}
}
in this code above, when navigating to this page an error is immediately thrown that this.hookNav
is undefined.
However, when I put the hookNav
function inside of a state object, like so
constructor(props) {
super(props);
state = {
hookNav : function() {
console.log("work please");
}
}
}
and in onPress
inside of renderItem
const { hookNav} = this.state;
return (
<TouchableOpacity style={{ margin: 9 }} onPress={hookNav}>
This works as intended.
It is my understanding that render
already has access to the component’s state
via this
. If renderItem
can access the state
object, through this
, why can it not access this.hookNav()
directly? Why does the function need to be encapsulated in an object for this to work?
Thanks.
It's because the value of this
inside any Javascript function depends on how that function was called.
In your non-working example, this.hookNav
is inside of the renderItem
method. So it will adopt the this
of that method - which, as I just said, depends on how it is called. Further inspecting your code shows that the renderItem
method isn't called directly by your component but passed as a prop (called renderItem
) of the FlatList
component. Now I don't know how that component is implemented (I've never used React Native), but almost certainly it calls that function at some point - and when it does, it can't be in the context of your component. This is because FlatList
can't possibly know where that function prop is coming from, and when it calls it, it will be treated as an "ordinary" function, not in the context of any particular object. So its this
context won't be your component instance, as intended, but will simply be the global object - which doesn't have any hookNav
method.
This problem doesn't happen in your second example because you simply access the function as part of this.state
- there is no passing of a method, like the renderItem
in the previous example, which depends on an internal this
reference. Note that this won't be the case if your hookNav
refers to this
inside it.
Such problems with this
context are very common in React, but fortunately there are two general solutions which will always fix this:
Bind methods in your component's constructor. If the constructor, in the first example, includes the statement this.renderItem = this.renderItem.bind(this);
, then it would work fine. (Note that this is recommended in the official React docs.) It's the "best" solution technically (performance is slightly better than in the other option) - but does involve quite a bit of boilerplate if you have lots of methods which need such binding.
Use arrow functions to define the methods instead. Instead of renderItem({ item, index }) {...}
, write it as renderItem = ({ item, index }) => {...}
. This works because arrow functions adopt the this
of their lexical scope, which will be the class itself - so in other words this
will always refer to the component instance, as you almost always want.
Don't worry if this is confusing - the behaviour of the this
keyword in JS is a common stumbling block for beginners to JS, and often enough for those more experienced too. There are a number of good explanations online which demystify it, of which I can especially recommend this one.