How to learn react logo

These tutorials will give you a deeper understanding of React.js. Learn with examples from the Javascript UI library, covering the basics and the advanced.

Browse by tag:

Implementing a Higher Order Component to conditionally render content

Components

A Higher Order Component (HOC) is an advanced React JS pattern that allows us the capability of reusing component logic. Check out the HOC Docs.

This paradigm allows us developers the ability to encapsulate some shared functionality between components and avoid repeating ourselves. An HOC takes in a component as an argument, adds some functionality to that supplied component and returns it.

Higher Order Component Explained

For the exercise, we'll simply start by creating a ClickButton component that gets passed a function and a count from state using props.

// index.js

import React from "react";
import ReactDOM from "react-dom";
import ClickButton from "./components/Buttons/ClickButton";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0
    }
  }

  incrementCount = () => {
    this.setState(prevState => {
      return {
        count: prevState.count + 1
      };
    });
  };

  render() {
  return (
    <div className="App">
      <ClickButton incrementCount={this.incrementCount} count={this.state.count} />
    </div>
  );
  }
}

ReactDOM.render(<App />, document.getElementById("root"););

// /Buttons/ClickButton.js

import React from "react";

const ClickButton = props => {
  return (
    <button onClick={props.incrementCount}>Plus one: {props.count}</button>
  );
};

export default ClickButton;

Let's say we want to extend the functionality from one component to another while keeping it in its seperate context:

  1. We'll build a ButtonGenerator component in /components/HOC/ButtonGenerator.js
  2. We'll hold the state (count and incrementCount) on this file
  3. The ButtonGenerator will be a functional component that takes in a Button as an argument: const ButtonGenerator = Button =>
  4. We'll return an anonymous class component class extends React.Component {... where state will be held
  5. We'll simply render the Button that we passed to our ButtonGenerator and export default ButtonGenerator;
import React from "react";

const ButtonGenerator = Button => // Step 2
  class extends React.Component { // Step 3
    constructor() {
      super();
      this.state = { // Step 1 and 4
        count: 0
      };
    }
    incrementCount = () => { // Step 1 and 4
      this.setState(prevState => {
        return {
          count: prevState.count + 1
        };
      });
    };

    render() {
      return (
        <Button incrementCount={this.incrementCount} count={this.state.count} /> // Step 5
      );
    }
  };

export default ButtonGenerator; // Step 6

index.js no longer holds the state. Instead, we pass in the ButtonGenerator and create a wrapped component called HOCClickButton.

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

import ClickButton from "./components/Buttons/ClickButton";

import ButtonGenerator from "./components/HOC/ButtonGenerator";

const HOCClickButton = ButtonGenerator(ClickButton);

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <HOCClickButton />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Here are the basic building blocks to create another button

// From index.js

import DoubleClickButton from "./components/Buttons/DoubleClickButton";
const HOCDoubleClickButton = ButtonGenerator(DoubleClickButton);
<HOCDoubleClickButton />

// DoubleClickButton.js

import React from "react";

const DoubleClickButton = props => {
  return (
    <button onDoubleClick={props.incrementCount}>
      Double Click Button {props.count}
    </button>
  );
};

export default DoubleClickButton;

We can use the same logic to create a higher order authenticate component that will wrap our app and conditionally render a login form or the app itself.

// index.js

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

// App.js

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

import ClickButton from "./components/Buttons/ClickButton";
import DoubleClickButton from "./components/Buttons/DoubleClickButton";

import ButtonGenerator from "./components/HOC/ButtonGenerator";

const HOCClickButton = ButtonGenerator(ClickButton);
const HOCDoubleClickButton = ButtonGenerator(DoubleClickButton);

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <HOCClickButton />
        <HOCDoubleClickButton />
      </div>
    );
  }
}

export default App;

Create a <AppPage /> in your root directory.

  • You'll have to move a lot of what is rendered in app.js to this new component
  • This is to ensure that we clean up our App component a little bit before we re-factor it to be wrapped up in an HOC
  • In app.js, render the PostsPage component.
  • Make sure the app working as it was before since it has been re-factored now.
// AppPage.js

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

import ClickButton from "./components/Buttons/ClickButton";
import DoubleClickButton from "./components/Buttons/DoubleClickButton";

import ButtonGenerator from "./components/HOC/ButtonGenerator";

const HOCClickButton = ButtonGenerator(ClickButton);
const HOCDoubleClickButton = ButtonGenerator(DoubleClickButton);

class AppPage extends React.Component {
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <HOCClickButton />
        <HOCDoubleClickButton />
      </div>
    );
  }
}

export default AppPage;

// App.js
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

import AppPage from "./AppPage";

class App extends React.Component {
  render() {
    return (
      <div>
        <AppPage />
      </div>
    );
  }
}

export default App;

Building the Higher Order Component

  • Create a Authentication.js file in the /HOC folder.
  • The Authenticate component should be able to take in another component as an argument, and it will return an anonymous class component.
  • Inside of 's render method, you'll want to return the App component.
  • export default Authenticate;.
// Authenticate.js

import React from "react";

const Authenticate = App =>
  class extends React.Component {
    render() {
      return <App />;
    }
  };

export default Authenticate;
  • Head over to App.js and import in our new Authenticate Higher Order Component and pass in App.
  • If this worked right then everything should render as it used to.
// App.js

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

import AppPage from "./AppPage";
import Authenticate from "./components/HOC/Authenticate";

class App extends React.Component {
  render() {
    return (
      <div>
        <AppPage />
      </div>
    );
  }
}

export default Authenticate(App);

Build out the LoginPage component. You can design it how you like

  • In your components directory, create a folder called Login and add a file called Login.js.
  • There should be a username input, a password input, and a Login button.
  • The component should invoke a handleLoginSubmit when somebody logs in.
  • This function should set a username on localStorage. You'll need to check local storage to see if a user is logged in.
  • Be sure to force the page to reload when a user logs in so that our component un-mounts and mounts again.
import React, { Component } from "react";

class Login extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: ""
    };
  }

  handleInputChange = e => {
    this.setState({ [e.target.name]: e.target.value });
  };

  handleLoginSubmit = e => {
    const user = this.state.username;
    localStorage.setItem("user", user);
    window.location.reload();
  };

  render() {
    return (
      <form className="login-form">
        <h3>Login</h3>
        <input
          type="text"
          placeholder="Username"
          name="username"
          value={this.state.username}
          onChange={this.handleInputChange}
        />
        <input
          type="password"
          placeholder="Password"
          name="password"
          value={this.state.password}
          onChange={this.handleInputChange}
        />
        <br />
        <br />
        <button onClick={this.handleLoginSubmit}>
          Log In
        </button>
      </form>
    );
  }
}

export default Login;

Extending the functionality of the Authenticate higher order component to conditionally render Login or App

  • Inside of Authenticate we need to add a constructor to hold our state data.
  • On state we need an isLoggedIn boolean flag and set it to false
  • On componentDidMount we need to check localStorage to see if a user is logged in.
  • Inside of the render function we will check if a user isLoggedIn
  • If a user is logged in we will return the <App />, else we will return the <Login />
import React from "react";

const Authenticate = App =>
  class extends React.Component {
    render() {
      return <App />;
    }
  };

export default Authenticate;

// becomes

import React from "react";
import Login from "../Login/Login";

const Authenticate = App =>
  class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        isLoggedIn: false
      };
    }
    componentDidMount() {
      if (!localStorage.getItem("user")) {
        this.setState({ isLoggedIn: false });
      } else {
        this.setState({ isLoggedIn: true });
      }
    }
    render() {
      if (this.state.isLoggedIn) return <App />;
      return <Login />;
    }
  };

export default Authenticate;

https://codesandbox.io/s/q3rm9n9y4q

How To Learn React is designed and developed by me, Blake Fletcher. It is a project intended to share the things I learn while working with the UI library. As I build things, complete tutorials, read blog posts, and watch videos, I'll add to the site. I hope to eventually bring on other contributors. If interested, send me an email at blkfltchr@gmail.com.