Samiksha Jagtap
Samiksha Jagtap

Reputation: 603

How to access prop outside of class while calling higher order component such that Higher order component will be reusable

I have created higher order component to render loader, if data is not available. component named as Loader is higher order component. and component named as ContactList calls the Higher order component. My code for ContactList component:

import React, { Component } from 'react';
import '../App.css';
import Loader from './hoc/Loader.js';

class ContactList extends Component {
  constructor(props) {
    super(props);
    this.state = {
    }
  }

 render() {
   return (
     <div className="App">
       {
        this.props.contacts.map((contact, index) => (
          <div key={index}>
            <p>{contact.name}</p>
          </div>
        ))
      }
    </div>
   );
  }
}

export default Loader(this.props.contacts)(ContactList);

code for Loader(HOC) component -

import React, { Component } from 'react';
import './Loader.css';

const Loader = (propName) => (WrappedComponent) => {
    return class Loader extends Component {
        isEmpty(prop) {
            return (
                prop === null ||
                prop === undefined ||
                (prop.hasOwnProperty('length') && prop.length === 0) ||
                (prop.constructor === Object.keys(prop).length === 0)
            );
        }
        render() {
            return this.isEmpty(propName) ? <div className="loader"></div> : <WrappedComponent {...this.props}/>
        }
    }
}

export default Loader;

But i am getting this error

Uncaught TypeError: Cannot read property 'contacts' of undefined as props are not accessible outside of class.

I have tried many solutions -

1) one of tried solution

2) If I change my export statement to export default Loader(ContactList), then this works fine but the loader component is no more reusable

Upvotes: 3

Views: 2649

Answers (5)

Samiksha Jagtap
Samiksha Jagtap

Reputation: 603

I changed the way of passing and accessing data. My changed code is as follow -

Code for ContactList component

import React, { Component } from 'react';
import '../App.css';
import Loader from './hoc/Loader.js';

class ContactList extends Component {
  constructor(props) {
    super(props);
    this.state = {
    }
  }

  render() {
    return (
      <div className="App">
        {
          this.props.contacts.map((contact, index) => (
            <div key={index}>
              <p>{contact.name}</p>
            </div>
          ))
        }
      </div>
    );
  }
}

export default Loader('contacts')(ContactList);

Code for Loader (either Higher order component) -

import React, { Component } from 'react';
import './Loader.css';

const Loader = (propName) => (WrappedComponent) => {
    return class Loader extends Component {
        isEmpty(prop) {
            return (
                prop === null ||
                prop === undefined ||
                (prop.hasOwnProperty('length') && prop.length === 0) ||
                (prop.constructor === Object.keys(prop).length === 0)
            );
        }
        render() {
            return this.isEmpty(this.props[propName]) ? <div className="loader"></div> : <WrappedComponent {...this.props}/>
        }
    }
}

export default Loader;

Upvotes: 0

Dacre Denny
Dacre Denny

Reputation: 30360

The problem appears to be the way you're trying to access props:

export default Loader(this.props.contacts /* <-- this is incorrect */ )(ContactList);

Rethink the situation - what is it you're trying to achieve? What does this mean in the context of your module?

Usually when using HOC's, you will find that a pattern similar to this emerges:

export default Loader(ContactList);

Which (depending on the implementation of Loader) would then allow you to import that component (ie LoaderContactList) and pass props "through" Loader down to the inner ContactList as follows:

<LoaderContactList contacts={ [ 'bob', 'joe', 'mary' ] } />

Hopefully this offers some clarification.

Update illustrating HOF/HOC concept

To illustrate the HOC concept described in my answer, see the following:

export function(InnerComponent) { // This is the higher-order-function to export (ie Loader)

    return class OuterComponent extends Component { // This is the higer-order-component

        render() {
            /* To illustrate the possiblitiy of reusable code via HOC's, 
               this HOC responds to an generic 'isBusy' prop by returning 
               a loading message if truthy */
            if(this.props.isBusy) {
                return <p>Loading</p>
            }

            /*
            Otherwise, the inner component that you pass to the higer- 
            order-function is rendered. The "pass through" concept I 
            discuss is shown here. Notice how all props from the 
            OuterComponent are passed through to the InnerComponent (ie 
            ContactList) 
            */
            return <InnerComponent { ...this.props } />
        }
    }
}

Upvotes: 0

Azamat Zorkanov
Azamat Zorkanov

Reputation: 819

Here:

export default Loader(this.props.contacts)(ContactList);

You're trying to access props outside of class. It is impossible to access them out of your ContactList class.

EDIT

If you need to show loader, depending on contacts array, you don't need to use HOC. Just use Loader inside of your template:

<Loader contacts={this.props.contacts}>
      {
        this.props.contacts.map((contact, index) => (
          <div key={index}>
            <p>{contact.name}</p>
          </div>
        ))
      }
</Loader>

Note, that in that case, you have to make your Loader just a component, not HOC.

Here the example of such case:

class Loader extends React.Component {
  render(){
    const {values} = this.props;
    if (values.length) {
       return this.props.children;
    }
    return <div>Loading...</div>
  }
 }

And use it in ContactListComponent:

import React, { Component } from 'react';
import '../App.css';
import Loader from './hoc/Loader.js';

class ContactList extends Component {
  constructor(props) {
    super(props);
    this.state = {
    }
  }

 render() {
   return (
     <div className="App">
       <Loader values={this.props.contacts}>
       {
        this.props.contacts.map((contact, index) => (
          <div key={index}>
            <p>{contact.name}</p>
          </div>
        ))
      }
      </Loader>
    </div>
   );
  }
}

export default ContactList;

The Loader component just example, use your own Loader. As I've said, there are no need to use HOC. Anyway, your Loader component will be reusable, because you can use it in different component also. Hope this helps

Upvotes: 1

Praveen Rao Chavan.G
Praveen Rao Chavan.G

Reputation: 2860

Your code should look like this

HOC :

const Loader = (props) => (WrappedComponent) => (moreProps) => {
const ContactList = props.ContactList;
 return (
   <Wrapper>
     <Container>
       <WrappedComponent {...moreProps} />
     </Container>
   </Wrapper>
 )
}

Now you can use it like this

export default Loader({ [ 'Praveen', 'Hari', 'Swamy' ] })(ContactList)

Upvotes: 0

Nima habibkhoda
Nima habibkhoda

Reputation: 253

export default Loader(this.props.contacts)(ContactList);

this is wrong , you have to set one let variable out side of class and set it inside of class , then call it there

Upvotes: 0

Related Questions