Mobile/Desktop View Management with HOCs in React
Tags: react, javascript, html
Published: 2019-10-17 15:36:51

DISCLAIMER: If you're unfamiliar with React's HOCs or state management in general, I recommend doing further research before proceeding with this article.

## Introduction

When designing a web application a common concern is managing desktop and mobile views. Utilizing React's Higher Order Components (HOCs) and context API, we can render components based on if a mobile layout is detected.

The goal is to abstract a React Context consumer and provider into their own HOCs. Then use the provider HOC to wrap the main App component, and use the consumer HOC to wrap any components that need to render differently based on mobile being detected. This is a normal HOC/Context pattern, the chief difference being we need a custom provider HOC to encapsulate updates on resizing of the window.

With this high-level picture in our heads, let's dive into the code.

## Setup
Clone the project [here](https://github.com/BennettDixon/mobile_view_management) and remove the `src/components/Mobile` directory if you want to follow along (`rm -rf src/components/Mobile`)

Create a new component directory called `Mobile` in `src/components/` (the directory you just deleted).

Create a file called `index.js` in the new directory. We will update this with exports later.

Finally run `npm run start` to start the React development server.


## Context
Next let's create the Context our HOCs will provide and consume in a file called `context.js`:
```javascript
import React from "react";

const IsMobileContext = React.createContext(false);

export default IsMobileContext;

```

This just creates a basic React Context with a default value of false. We will use this as described in our HOCs.


## HOCS
Let's create the higher-order components that will allow us to easily use the context we just created.
### Context.Provider HOC
We must first create a Context provider HOC to register our App component with.
```javascript
import React from "react";
import IsMobileContext from "./context";

const INITIAL_STATE = {
  size: {
    width: window.innerWidth,
    height: window.innerHeight
  }
};
```
Import React for `JSX` access and our Context we just created. Define the initial state for our provider HOC. We could set this to a boolean, but I find a size object to be more expandable.

Next let's create the provider HOC itself:
```javascript
const withIsMobileViewProvider = Component => {
  class WithIsMobileViewProvider extends React.Component {
    constructor(props) {
      super(props);
      this.state = INITIAL_STATE;
    }

    // add listener to handle window resizing
    componentDidMount() {
      window.addEventListener("resize", this.handleWindowSizeChange);
    }

    handleWindowSizeChange = event => {
      this.setState({
        size: { width: window.innerWidth, height: window.innerHeight }
      });
    };

    render() {
      // current logic to determine if isMobileView
      const isMobileView = this.state.size.width <= 600;
      return (
        <IsMobileContext.Provider value={isMobileView}>
          <Component {...this.props} />
        </IsMobileContext.Provider>
      );
    }
  }
  return WithIsMobileViewProvider;
};

// finally export the HOC
export default withIsMobileViewProvider;
```
This is a simple React HOC setup. Once the component mounts, we add our event listener to handle when the window resizes. The handler calls a simple function that sets the new width and height in our `state.size`, forcing the HOC to re-render, and in-turn re-render its child component.

During the render we determine if it's a mobile view. Then pass the resulting boolean to the Context provider for use by consumers and render the component normally.

### Context.Consumer HOC
We must now create a HOC to consume the Context we previously created and will provide to via the provider HOC we just created.
```javascript
import React from "react";
import IsMobileContext from "./context";
```
Similarly, import our Context and React for access to `JSX`. Our consumer HOC doesn't manage state as it just passes the value we provide via the Context to its child.
```javascript
const withIsMobileView = Component => {
  class WithIsMobileView extends React.Component {
    render() {
      return (
        <IsMobileContext.Consumer>
          {isMobileView => {
            return <Component {...this.props} isMobileView={isMobileView} />;
          }}
        </IsMobileContext.Consumer>
      );
    }
  }
  return withIsMobileView;
};

// finally export the HOC
export default withIsMobileView;
```
This render method registers a consumer for our Context, which receives the boolean value we defined in our provider HOC. We then pass this to our component as a prop called `isMobileView`.

## Exports
Now that we have created our Context and HOCs we must export them for use in other components. Update our `index.js` file:
```javascript
import withIsMobileView from "./withIsMobileView";
import withIsMobileViewProvider from "./withIsMobileViewProvider";
import IsMobileContext from "./context";

export { IsMobileContext, withIsMobileView, withIsMobileViewProvider };

```

## Use
Now we have created our HOCs for managing the view state of our application.

First we must register a provider for our consumers to actually pull the value from and pass to their children. We will do this in the App component as it is our parent component for the application.

Navigate to the App component (`src/components/App/index.js`) and import the provider HOC we just created:
```javascript
import { withIsMobileViewProvider } from "../Mobile";
```
Next use the HOC with our App component on export. Update the last line of the file from:
```javascript
export default App;
```
to:
```javascript
export default withIsMobileViewProvider(App);
```

Congratulations you just used a HOC to give a Context provider to our over-arching App component! Now we need to do the same with the consumer HOC in any views we want to determine are mobile.

Let's head over to the LandingPage component (`src/components/Landing/index.js`) and use this consumer with our landing page to display a mobile landing page for our users. Import the consumer HOC we created, similar to when we imported the provider HOC in the App component.
```javascript
import { withIsMobileView } from "../Mobile";
```

Next we need to register our LandingPage component with the HOC when we export it, same as the App.
```javascript
export default withIsMobileView(LandingPage);
```

Now our component is receiving the `isMobileView` prop via the HOC system we just created. Its value will also be automatically updated upon window resizing. However we are not rendering different pages based on this value, so let's change that.

You can see I have created the components `MobileLandingPage` and `DesktopLandingPage` with a simple h1 tag to demonstrate this.
```javascript
const MobileLandingPage = () => {
  return <h1>Mobile Landing Page</h1>;
};

const DesktopLandingPage = () => {
  return <h1>Desktop Landing Page</h1>;
};
```
However our landing page is not using them yet, let's change that too. With a simple `JSX` expression we can conditionally render either component based on our `isMobileView` boolean value. Change the render method of our `LandingPage` component to implement this logic:
```javascript
class LandingPage extends React.Component {
  render() {
    return (
      <>
        {this.props.isMobileView ? (
          <MobileLandingPage />
        ) : (
          <DesktopLandingPage />
        )}
      </>
    );
  }
}
```

That's it! Save the file and check out the effect in your browser. Either use developer tools to swap to a mobile layout or simply resize your window to a point that our HOC determines it's mobile. 

One cool feature of this system is the only value provided is a boolean. This means you can do a plethora of things with it, from dynamic rendering like you see above to simply changing the styling on a couple elements. You could even switch up the HOC system to provide the size object we created in addition to or instead of the boolean!

I hope this post was insightful into one of the many ways you can manage mobile and desktop views when building scalable and reliable web applications.

Feel free to follow me on [twitter](https://twitter.com/bennett_32) to stay up to date on my latest shenanigans!