Reputation: 894
I am passing an event handler showSpinner()
from parent component. This method displays the activity Indicator in my app, the method when called from the parent class, works. But when I pass it down to a child component and then call it from the child as this.props.showSpinner()
, I am getting the error
TypeError: undefined is not an object
(evaluating 'Object.keys(this.state.data)')
I am also not able to console.log
the method at the child's props
Please note, I have already bound the function at the parent.
Here is a part of my code. This is the parent component.
import React from 'react';
import { View, Button, Alert, Image, ScrollView, BackHandler, TouchableOpacity,Text, ActivityIndicator } from 'react-native';
import ProductListingItem from '../ProductCategories/ProductListingItemCategories.js';
import PusherColumnCategories from '../ProductCategories/PusherColumnCategories.js';
import NavRightButton from '../NavButton/NavRightButton.js';
import ActivitySpinner from '../ActivitySpinner.js';
const TAG = 'PRODUCTCATEGORIESPAGE';
export default class ProductCategoriesPage extends React.Component {
constructor(props) {
super(props);
/*this._getResponseFromApi = this._getResponseFromApi.bind(this);*/
this._onPressGoToCart=this._onPressGoToCart.bind(this);
if(props){
/*console.log(TAG,'constructor()','props available');*/
console.log(TAG,'constructor()','props JSON stringified = '+JSON.stringify(props));
/*this.setState({dataMain : (props.navigation.state.params.categories)});*/
}
this.state = {
dataMain: props.navigation.state.params.categories,
showIndicator: false,
};
console.log(TAG,'constructor','this.state.dataMain = '+this.state.dataMain );
}
static navigationOptions = ({navigation}) => {
return{
title: 'Categories',
headerLeft: null,
headerStyle: {
backgroundColor: '#EE162C',
},
/*headerBackTitleStyle: {
color: 'white',
},*/
headerTintColor: 'white',
headerRight: <NavRightButton navigation= {navigation}/>,
gesturesEnabled:false,
};
};
_onPressGoToCart(){
console.log(TAG,'_onPressGoToCart');
console.log(TAG,'_onPressGoToCart','navigation props ='+JSON.stringify(this.props));
const { navigate } = this.props.navigation;
navigate('CartPage');
}
componentWillReceiveProps(newProps){
console.log(TAG+'componentWillReceiveProps');
if(newProps){
console.log(TAG,'componentWillReceiveProps()','props available');
console.log(TAG,'componentWillReceiveProps()','props = '+newProps.navigation.state.params.categories);
}
}
_OnAlert(title,message){
console.log(TAG,'_onAlert');
Alert.alert(
title,
message,
[
{text:'done',onPress: () => { }}
]
);
}
componentDidMount () {
console.log(TAG,'componentDidMount');
/*this._getResponseFromApi();*/
BackHandler.addEventListener('hardwareBackPress',() => {return true});
}
componentWillMount () {
console.log(TAG,'componentWillMount');
}
_showSpinner(){
console.log(TAG,'_showSpinner');
this.setState({
showIndicator:true,
});
}
_hideSpinner(){
console.log(TAG,'_hideSpinner');
this.setState({
showIndicator:false,
});
}
render(){
console.log(TAG,'render');
console.log(TAG,'render','dataMain = '+this.state.dataMain[0].id);
// console.log(TAG,'render','showSpinner = '+JSON.stringify(this.showSpinner()));
// var tempshowspinner = this.showSpinner.bind(this);
// console.log(TAG,'render','tempshowspinner = '+JSON.stringify(tempshowspinner));
return(
<View
style={{
flex:1,
}}>
<ScrollView style = {{flex:1,
backgroundColor:'#F2F2F2',
}}>
<View style = {{
flex:1,
flexDirection:'column',
}}>
<PusherColumnCategories style = {{
flex:1,
}}
data = {this.state.dataMain}
navigate = {this.props.navigation}
showSpinner = {this._showSpinner}
hideSpinner = {this._hideSpinner}/>
</View>
</ScrollView>
<ActivitySpinner showIndicator={this.state.showIndicator}/>
</View>
);
}
}
This is the corresponding child component.
import React from 'react';
import {View, Component, Button} from 'react-native';
import ProductListingItem from './ProductListingItemCategories';
const TAG = "PUSHERCOLUMNCATEGORIES";
export default class PusherColumnCategories extends React.Component {
constructor(props){
super(props);
if(props){
console.log(TAG,'props ='+JSON.stringify(props));
/*console.log(TAG,'props data length = '+Object.keys(props.dataMain).length);*/
console.log(TAG,'Props = '+ JSON.stringify(props.data));
console.log(TAG,'Navigation Props = '+JSON.stringify(props.navigate));
}
this.state = {
data: props.data,
propsAvailable: false,
navigate: props.navigation,
};
};
componentDidMount(){
console.log(TAG,'componentDidMount');
}
componentWillReceiveProps(newProps){
console.log(TAG,'componentWillReceiveProps',newProps.data);
this.setState({
/*data: JSON.parse(JSON.stringify(newProps.data)),*/
data: (newProps.dataMain),
}, function() {
console.log(TAG,'componentWillReceiveProps','this.setState()','data = '+(Object.keys(this.state.data)));
});
}
componentDidUpdate(){
console.log(TAG,'componentDidUpdate');
}
render(){
console.log(TAG,'render()');
if(this.state.data){
console.log(TAG,'render()','state not empty');
console.log(TAG,'render()','data product_code = '+this.state.data[1].product_code);
return(
<View style = {{
flex:1,
flexDirection: 'column',
}}>
<Button
style = {{
flex:1,
}}
title = 'presshere'
onClick = {this.props.showSpinner}
/>
<RenderColumn style = {{
flex:1,
}}
data = {this.state.data}
navigate = {this.props.navigate}
showSpinner = {this.props.showSpinner}
hideSpinner = {this.props.hideSpinner}/>
</View>
);
} else {
console.log(TAG,'render()','state empty');
return(
<View style = {{
flex:1,
flexDirection: 'column',
}}/>
);
}
};
}
Upvotes: 0
Views: 2957
Reputation: 440
EDIT: I actually found the real problem. The binding stuff is good to know, but it's not the answer to the actual question. The problem is newProps.dataMain
. newProps
does not have a dataMain
key on it, it's actually data
as you can see below
<PusherColumnCategories style = {{
flex:1,
}}
data = {this.state.dataMain} // the key is `data`, not `dataMain`
navigate = {this.props.navigation}
showSpinner = {this._showSpinner}
hideSpinner = {this._hideSpinner}/>
So in this piece of code in componentWillReceiveProps
this.setState({
/*data: JSON.parse(JSON.stringify(newProps.data)),*/
data: newProps.data /* not newProps.dataMain */, // newProps.dataMain is not an actual key, so it will set `data` to undefined
}, function() {
console.log(TAG,'componentWillReceiveProps','this.setState()','data = '+(Object.keys(this.state.data))); // if you call `Object.keys` on undefined, you get the error that you posted
});
When you do myFunction.bind(obj)
, you create a new function that wraps your existing function and remembers the object obj
you pass in, and whenever you call that new function, it calls your original function with this
set to obj
.
In your _showSpinner
an _hideSpinner
functions, you make use of this.setState
, so it's important that this
is set to your parent component so that you update the state of parent component, and you're trying to make sure of that, but you're also binding unnecessarily at a lot of places, like in the constructor of ProductCategoriesPage
, which is unnecessary. And you also want to remove bind(this)
from the following
tempshowspinner
in the render
function of PusherColumnCategories
. this.props.showSpinner
already updates the parent component since it's bound to it. So it's redundant binding it here again, which gives the wrong impression that you're binding it again to the child component, which is not the case.
The bind(this)
in the Button
component right below. You don't want to bind over here for the same reason
Upvotes: 1