mad_greasemonkey
mad_greasemonkey

Reputation: 894

Event Handler (prop) passed to child component cannot be called react native

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

Answers (1)

Ambyjkl
Ambyjkl

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

Related Questions