Evheniy Bystrov

@evheniybystrov

React Web Project Building. Part 2 — HOCs

In previous part we created AppKernel HOC (higher-order component) to add some useful features for your app.

In this part we will continue work with HOCs and functional programming. We will split our big HOC into some small parts which you can reuse to build your own app HOC with only useful parts.

But before we start, we need to remember some functional programming basics.

HOC (higher-order component) is a kind of higher-order function. It’s a function which has component as an argument and it returns a new component.

In this part we will use functional composition and some kind of curry.

Let’s remember our AppKernel from the pervious part:

import React, { StrictMode, Suspense } from 'react';
import {
ErrorBoundary,
FallbackView,
} from 'react-error-boundaries';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';

import store from '../store';

const AppKernel = (Component) => {
return () => (
<StrictMode>
<ErrorBoundary FallbackComponent={FallbackView}>
<Provider store={store}>
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Component />
</Suspense>
</Router>
</Provider>
</ErrorBoundary>
</StrictMode>

);
};

export default AppKernel;

And just imagine our goal:

import { compose } from './helpers';

import strictMode from './hocs/strictMode';
import errorBoundary from './hocs/errorBoundary';
import redux from './hocs/redux';
import router from './hocs/router';
import suspense from './hocs/suspense';

const AppKernel = compose(
strictMode,
errorBoundary,
redux,
router,
suspense,
)
;

export default AppKernel;

It looks more declarative — less code with the same functionality. And other benefit — you can compose any part in the HOC and reuse it in any other React app.

Who wants to see all code now, I created a github repository.

So, let’s start form the beginning.

First of all, we need to create compose function. For this I created a helper inside of our src/app directory. I didn’t created own compose function because we use redux, it has own compose function and it works perfect for us.

import { compose } from 'redux';

The main idea is making small HOCs for each layer. For this I created a wrapper:

const wrapper = (WrapperComponent, wrapperProps = {}, Component) => () => (
<WrapperComponent {...wrapperProps}>
<Component />
</WrapperComponent>
);

It gets WrapperComponent like StrictMode or Suspense and wraps the Component. We can add any property if we need it, for example if we need to pass store in Redux Provider:

<Provider store={store}>

But to use it in composition we need to curry this function:

const hocCreator = (WrapperComponent, wrapperProps, Component) => {
if (!WrapperComponent) {
throw new Error('WrapperComponent should be set!');
}

return Component
? wrapper(WrapperComponent, wrapperProps, Component)
: Cmp => {
if (!Cmp) {
throw new Error('Component should be set!');
}

return wrapper(WrapperComponent, wrapperProps, Cmp);
}
};

It’s uses wrapper function with some checking: if we didn’t put the Component it returns another function.

And all code of our helper:

import React from 'react';
import { compose } from 'redux';

const wrapper = (WrapperComponent, wrapperProps = {}, Component) => () => (
<WrapperComponent {...wrapperProps}>
<Component />
</WrapperComponent>
);

const hocCreator = (WrapperComponent, wrapperProps, Component) => {
if (!WrapperComponent) {
throw new Error('WrapperComponent should be set!');
}

return Component
? wrapper(WrapperComponent, wrapperProps, Component)
: Cmp => {
if (!Cmp) {
throw new Error('Component should be set!');
}

return wrapper(WrapperComponent, wrapperProps, Cmp);
}
};

export {
hocCreator,
compose,
};

Now we are ready to create a HOC for each layer.

StrictMode

src/app/hocs/strictMode.js:

import { StrictMode } from 'react';

import { hocCreator } from '../helpers';

export default hocCreator(StrictMode);

It’s too easy. We create a HOC from the React StrictMode component using our hocCreator helper function.

If we imagine how it works we get something like this:

() => (
<StrictMode>
<Component />
</StrictMode>
)

ErrorBoundary

src/app/hocs/errorBoundary.js:

import { ErrorBoundary, FallbackView } from 'react-error-boundaries';

import { hocCreator } from '../helpers';

export default hocCreator(ErrorBoundary, { FallbackComponent: FallbackView });

Here we can see how I use properties for ErrorBoundary component.

It works like this:

() => (
<ErrorBoundary FallbackComponent={FallbackView}>
<Component />
</ErrorBoundary>
)

Redux

src/app/hocs/redux.js:

import { Provider } from 'react-redux';

import { hocCreator } from '../helpers';
import store from '../../store';

export default hocCreator(Provider, { store });

Router

src/app/hocs/router.js:

import { BrowserRouter } from 'react-router-dom';

import { hocCreator } from '../helpers';

export default hocCreator(BrowserRouter);

Suspense

src/app/hocs/suspense.js:

import React, { Suspense } from 'react';

import { hocCreator } from '../helpers';

export default hocCreator(Suspense, { fallback: <div>Loading...</div> });

And small bonus. I updated our page component to use only pure components. Pure components use the same idea like pure functions — no side effects.

It’s function with props arguments and it returns JSX:

props => JSX

src/page/index.js:

import React from 'react';
import logo from '../logo.svg';
import './index.css';

const PageComponent = () => (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/page/index.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);

export default PageComponent;

More by Evheniy Bystrov

Topics of interest

More Related Stories