DEV Community

Edem Agbenyo
Edem Agbenyo

Posted on • Updated on

Use HOC, Render Props and Context to Build better components with React.

Alt Text
React is such a powerful library, that everyone with knowledge of the basics can build a really good application. Managing state in react is built out of the box with React own state management APIs.
But as your app gets more complex, it becomes harder to track, get a better hold of your state and understand what is going on. In order to improve the understanding of your code at such a moment, React has made available techniques and APIs that helps us build components that seamlessly work.
Some of those techniques and API are:

  • HOC(Higher Order Component)
  • Render Props
  • React Context

HOC(Higher Order Component)

HOC is an advanced technique to React to reusing component logic. Just like a Higher Order Function, which receives a function as an argument and returns a function, a HOC takes a component as an argument and returns a new component.
Let's take this code for example:

import React from 'react'

function Students() {
  const students = [
    { name: "John", score: "A-" },
    { name: "Samuel", score: "B-" },
    { name: "Smith", score: "A+" },
    { name: "Mark", score: "A-" },
    { name: "Mike", score: "B-" },
    { name: "John", score: "B+" },
  ];

  return (
    <div>
      {students.map((student) => (
        <p>
          {student.name} - {student.score}
        </p>
      ))}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

From the snippet of code above, we could tell that the list of students and their grade is tied to the Student component. What happens when another component needs to make use of that same list? We do not want to copy and paste the same list across all components. But what we want is a reusable component that could be used by other components. This is where HOC shines, it allows us to create a Wrapper Component that provides other components with the data they need.

import React from "react"

function Students(props) {
  return (
    <div>
      {props.students.map((student) => (
        <p>
          {student.name} - {student.score}
        </p>
      ))}
    </div>
  );
}

const withStudents = (Component) => {
  const students = [
    { name: "John", score: "A-" },
    { name: "Samuel", score: "B-" },
    { name: "Smith", score: "A+" },
    { name: "Mark", score: "A-" },
    { name: "Mike", score: "B-" },
    { name: "John", score: "B+" },
  ];
  return () => <Component {...students}></Component>;
};

const ComponentWithStudents = withStudents(Students);

export default ComponentWithStudents;
Enter fullscreen mode Exit fullscreen mode

We create a withStudents component which accepts any component as argument and supplies data to it in the form of props. The wrapper component withStudents returns the supplied component by wrapping it in a container component, it does not alter the argument component in any way. HOC are pure functions with no side-effects. The syntax above will look familiar to you if you have worked with redux before.

We could pass extra parameters to our wrapper component by doing the following:

const withStudents = (count) => (Component) => {
  const students = [
    { name: "John", score: "A-" },
    { name: "Samuel", score: "B-" },
    { name: "Smith", score: "A+" },
    { name: "Mark", score: "A-" },
    { name: "Mike", score: "B-" },
    { name: "John", score: "B+" },
  ];
  const listStudentsLimited = students.slice(0, count);
  return () => <Component students={listStudentsLimited}></Component>;
};
const maxStudentCount = 3;

export default withStudents(maxStudentCount)(App);
Enter fullscreen mode Exit fullscreen mode

Our Students component remains the same while the withStudents wrapper now returns a function that wraps what was previously returned, making it a true Higher Order Function :).
Next, we will look at how we could use Render Props to do similar data sharing.

Render Props

The second way by which we can share data among components is with Render Props. From the reactjs.org, it defines render props to be A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic. So using our previous example, we create a render props component that surrounds the rendering part of the original Student component. The Render Props in turn returns the component as its child and passes any data to it.

import React from "react";

function Students() {
  return (
    <StudentWithRenderProps>
      {({ students }) => (
        <div>
          <h1>Students with grades</h1>
          {students.map((student) => (
            <p>
              {student.name} - {student.score}
            </p>
          ))}
        </div>
      )}
    </StudentWithRenderProps>
  );
}
const StudentWithRenderProps = (props) => {
  const students = [
    { name: "John", score: "A-" },
    { name: "Samuel", score: "B-" },
    { name: "Smith", score: "A+" },
    { name: "Mark", score: "A-" },
    { name: "Mike", score: "B-" },
    { name: "John", score: "B+" },
  ];
  return props.children({
    students,
  });
};

export default Students;
Enter fullscreen mode Exit fullscreen mode

Context

In React.js a Context provides a way to pass data through the component tree without having to pass props down manually at every level. This is one way to solve the component drilling issue, where you have to pass data through several components to share data with children located down in the components. Using Context makes it easier to share data among many components within an application; data such as user session, theme or language.
In our example, we are going to use a context to share user-session information among components that need it.

export const AuthContext = React.createContext({});

export default function App() {
  const userInfo = {
    name: "John Smith",
    email: "john@smith.com"
  };
  return (
    <AuthContext.Provider value={userInfo}>
      <Profile></Profile>
    </AuthContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

First, we create the context React.createContext({}) and assign it to a variable. This will be used to wrap any consuming component with the help of the Provider component that is made available by the context. The Provider accepts a value prop that contains the data to share among any nested components. In our case, we want to share the userInfo.

Next, for any component to access the data being shared by a context, we need to get a reference of the context and pass it to the useContext hook made available by React.

import { useContext } from "react";
import { AuthContext } from "./App";
export default function Profile() {
  const auth = useContext(AuthContext);
  console.log(auth);
  return (
    <div>
      User is
      <span style={{ color: "red" }}>
        {Object.keys(auth).length > 0 ? "Logged in" : "Logged out"}
      </span>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now, the Profile component has access to the userInfo from the AuthContext.

Both HOC and Render Props works almost in the same way. HOC works in a way that feels like it works behind the scene, while Render Props are more frontend centered. They are less code-intensive as compared to Context. Context on the other hand allows us to give all consuming components access to the data passed to the Provider.

Top comments (0)