Giter Club home page Giter Club logo

atomhoc's Introduction

Higher Order Component (HOC) in React

HOC

A higher order component (HOC) is a function that accepts your component as an argument and returns a new function that adds some feature to the component. If you're familiar with the concept of decorators than basically HOC can be thought as a decorator function.

@HOC
def ListComponent(props):
    # For simplicity ...

"Talk is cheap. Show me the code"

Assume we're fetching "heavy" data from some open API. For the sake of simplicity we'll use open API from randomuser.

Component: Feed

Any container that holds sequential data like users in our case but could be comments, posts, etc. can be thought of as Feed.
Basically we can think of a Feed as

Feed = [FeedItem_1, FeedItem_2, ..., FeedItem_n]

Basic Feed component could be written extending React.Component class as below:

class Feed extends Component {
  render() {
    console.log(this.props);
    const { loadingTime } = this.props;
    return (
      <div className="feed">
        {this.props.contacts.map(
          contact => <FeedItem contact={contact} />
        )}
        <p>Loading time {loadingTime} seconds</p>
      </div>
    );
  }
}

In this example our component displays the sequence of another component (child) FeedItem.

Component: FeedItem

FeedItem could be anything in reality. In our case since we're fetching the user's info - we display user's photo, name and surname.

const FeedItem = ({ contact }) => {
  return (
    <div className="col-sm-4 offset-4">
      <div className="row p-2" key={contact.email}>
          <div className="col-sm-2">
            <img
              className="rounded-circle"
              src={contact.thumbnail}
              role="presentation"
            />
          </div>
          <div className="feedData col-sm-9">
            <strong>{contact.name}</strong>
            <br />
            <small>{contact.email}</small>
          </div>
        </div>
    </div>
  );
};

Please note that since our component doesn't rely on state we make it functional instead of extending React.Component.

Component: App

Where do fetch(API) actually takes place and how do we populate our Feed component with the users's data? Ladies and gentlemen, our App compoenent is below:

class App extends Component {
    state = { contacts: [] };
  
    componentDidMount() {
      fetch("https://api.randomuser.me/?results=50")
        .then(response => response.json())
        .then(parsedResponse =>
          parsedResponse.results.map(user => ({
            name: `${user.name.first} ${user.name.last}`,
            email: user.email,
            thumbnail: user.picture.thumbnail
          }))
        )
        .then(contacts => this.setState({ contacts }));
    }
  
    render() {
      return (
        <div className="App">
          <nav class="navbar navbar-expand-sm bg-dark navbar-dark">
                <a class="nav-link" href="#">
                  <h2>Higher Order Component (HOC)</h2>
                </a>
          </nav>
  
          <Feed contacts={this.state.contacts} />
        </div>
      );
    }
}

Note here how the rendering happens. Inside our render() function - we pass our this.state.contacts data we've got from API.
For the first time before the data is loaded, our state is empty - this.state.contacts = [] which is why our users won't see anything at all.
At this point we don't have any errors or mistakes - our app is working but wouldn't it be nice if we could somehow tell our users that they see empty screen due to the loading time - some loading text, perhaps?
This is where we introduce the HOC concept.

Component(HOC): Loading

const isEmpty = prop =>
    prop === null ||
    prop === undefined ||
  ( prop.hasOwnProperty("length") && prop.length === 0 ) ||
  ( prop.constructor === Object && Object.keys(prop).length === 0 );

const Loading = loadingProp => WrappedComponent => {
  class LoadingHOC extends Component {

      componentDidMount() {
        this.startTimer = Date.now();
      }

      componentWillUpdate(nextProps) {
        if (!isEmpty(nextProps[loadingProp])) {
          this.endTimer = Date.now();
        }
      }

      render() {
        return isEmpty(this.props[loadingProp]) ? 
        (
          <div className="loader">
            <p>
              Loading started...
            </p>
          </div>
        ) : 
        (
          <WrappedComponent {...this.props} loadingTime={((this.endTimer - this.startTimer) / 1000).toFixed(2)} />
        );
      }
  };

  return LoadingHOC;
};

"What the hell have I just seen?" - yeah, I've got this feeling too, bro.
Before going futher, let me first explain the two arrow functions staying on top of the component declaration: const Loading = loadingProp => WrappedComponent => ....
As someone wise once said: "A picture is worth a thousand words" - in our case the example:

let add = (x, y) => x + y;

For humans the same function without shortcut => which hipsters like to call "arrow function".

function add(x, y) {
    return x + y;
}

// Somewhere in our code
add(3,4); // Works fine, returns 7.

Further, let's rewrite the above slightly different:

function add(x) {
    function inner(y) {
        return x + y;
    }
    return inner;
}

// Somewhere in our code
add(3)(4) // Works fine, returns 7.

So, what we did in the example above could be described in two words: closure and currying.
Take a minute of break if the concepts are new to you, you've earned it?
Feel good? Ok, then it's time to get from human's world back to Hipster's. Let's go.

const add = (x) => (y) => x + y;

Nice and easy. This is the same as the last example with the exception of using =>. The presense of more than one => tell us that the black magic nested function calls are present.
When JS interpreter see the first => it understands that he need to return everything to the right of that but then it meets another => to the right and that means another function - so, stop here and return that function till the best time next function invocation happens.

Now, let's come back to our original HOC component declaration, the line that said: "loadingProp => WrappedComponent => ...", remember?
Now, it should make sense, basically it says:

const Loading = function(loadingProp) {
    function inner(WrappedComponent) {
        // ...
    }
    return inner;
}

Now, the main question is:
Q: why do I need to know this => hipster's language to understand the HOC concept being used?
A: That's an excellent question, next question, plz.:-)

HOC: Loading(Feed)

Let's take a closer look at what happens inside the second => call.

(WrappedComponent) => {
    class LoadingHOC extends Component {

        componentDidMount() {
            this.startTimer = Date.now();
        }

        componentWillUpdate(nextProps) {
            if (!isEmpty(nextProps[loadingProp])) {
                this.endTimer = Date.now();
            }
        }

        render() {
            return isEmpty(this.props[loadingProp]) ? 
            (
            <div className="loader">
                <p>
                Loading started...
                </p>
            </div>
            ) : 
            (
            <WrappedComponent {...this.props} loadingTime={((this.endTimer - this.startTimer) / 1000).toFixed(2)} />
            );
        }
  };

  return LoadingHOC;
};

Here we define our new component called LoadingHOC which literally wraps our own Feed component and adds text <p>Loading Started</p> while we still fetch(...) ing data.
Whenever we fetch(...) ed data the state inside App is going to be updated which would cause it to re-render.
Here comes the important part: Within the App component we will pass the props to LoadingHOC component instead of calling and passing props directly to the Feed component.

"App.js" -> "LoadingHOC" -> "Feed"

This way the additional logic (in our case displaying <p>Loading started...</p>) wrapped completely inside the LoadingHOC component. That means that we can reuse our HOC with as many other components as we need.
Basically, that's the beauty of HOC concept in a way that we can 'inject' custom logic without ever touching the original component.
There is only one question that's probably confusing you: how do we actually wrap our Feed component by LoadingHOC component? Take a look below.

Wrapping

Since JavaScript has super unintuitive convinient feature called closure allowing us to export LoadingHOC component directly.
First of all inside Feed.js file we import our Loading component. You remember that double => hipster syntax, right?

import Loading from './HOC/Loading'

And at the end, after the Feed component's declaration, where you'd usually type:

export default Feed;

but instead "decorate" your component wrapping it in Loading HOC.

export default Loading("contacts")(Feed);

Isn't it nice?


Your friend,
Igor

atomhoc's People

Watchers

Igor Tarlinskiy avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.