Using the New Context API with Higher Order Components in React

No Comments

What is the new Context API and what problem does it solve?

React Context API is a feature used for sharing the global state and passing it all the way down through the components tree. This can be useful when we need to pass some props from the parent component A, down to the child component C which is a child of component B.

Let’s consider this hypothetical code example:

class A extends React.Component {
  state = {
    value: 'Some value'
  };
 
  render() {
    return(
      <B value={this.state.value} />
    );
  }
}
 
const B = (props) => (
  <C value={props.value} />
);
 
const C = (props) => (
  <div>{props.value}</div>
);

In this example, we are sending value from the parent component A through its child component B, to be used only inside the child component C. Component B doesn’t care about the value prop, and it’s only passing down that prop to be used in component C.

This is something commonly known as Prop Drilling – passing props down from an upper level to a lower level in the components tree, while components in between don’t care about these props.

When to use Context?

As React’s official documentation suggests, you shouldn’t use Context just to avoid passing props a few levels down, but rather when you need to share the same data in many different components at multiple levels (e.g. theming, localization, authentication, etc.).

// AuthUser.jsx
import React, { Component } from 'react';
import { authenticateUser } from './Auth';
 
// initialize Context with default value
export const AuthCtx = React.createContext(null);
 
const AuthenticateUser = (ComposedComponent) => {
  class Authenticate extends Component {
    state = {
      userAuth: null
    };
 
    async componentDidMount() {
      try {
        // make API request to authenticate user on the backend
        const { status, data } = await authenticateUser();
        if (status === 200) {
          this.setState({ userAuth: data });
        }
      } catch (err) {
        // handle error
      }
    }
 
    render() {
      const { userAuth } = this.state;
      return(
        <AuthCtx.Provider value={userAuth}>
          <ComposedComponent />
        </AuthCtx.Provider>
      );
    }
  }
  return Authenticate;
};
 
export default AuthenticateUser;

We’ve first created Context using the createContext() method, which returns Provider and Consumer objects. We are passing userAuth to the Provider’s prop, value making it available to all of its children, so that AuthCtx’s Consumer instance below in the hierarchy tree can access (consume) it.

We’ve implemented AuthenticateUser reusable Higher Order Component to abstract business logic. This HOC returns ComposedComponent passed as an argument that we’ve just wrapped into Context’s Provider component.

Next, we are going to use this Higher Order Component to enhance and wrap our NavBar component:

// NavBar.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import AuthenticateUser, { AuthCtx } from './AuthUser';
 
const NavBar = () => (
  <div className="nav-bar">
    <AuthCtx.Consumer>
      {userAuth =>
        !userAuth ? (
          <Link to="/login">Login</Link>
        ) : (
          <Link to="/profile">Profile</Link>
          <Link to="/logout">Logout</Link>
          ...
        )
      }
    </AuthCtx.Consumer>
  </div>
);
 
export default AuthenticateUser(NavBar);

Consumer subscribes to the context’s changes, and it uses the render props pattern to consume provided userAuth context.

Conclusion

Whenever we need to display some data only if a user is logged in, we can now simply wrap that component with the AuthenticateUser higher-order component to provide the context, and check if user is authenticated inside the <AuthCtx.Consumer> component, just like we did for the NavBar.

Bojan Aleksic

Software developer at codecentric Doboj since November 2016.

More content about JavaScript

Comment

Your email address will not be published. Required fields are marked *