EvsChen's Blog

Designing a higher-order components

September 15, 2018

After the internship at Baidu, I’ve learnt some techniques on creating a HOC. The concept of HOC seems to originate from React, which gives its definition in the official documentation.

A higher-order component is a function that takes a component and returns a new component

However, the concept of HOC can be applied to almost any modern JavaScript MVC or MVVM frameworks. At Baidu, we use San, which is quite similar to VueJS. However, the philosophy behind designing a HOC is the same.

Background

We have a quite comprehensive UI framework in use, san-xui, which almost covers all the business components that will be used.

I worked at the AI2B group at Baidu Cloud. We mainly build privatized system for companies to provide Baidu AI apis. At work we found that almost 90% percent of our pages look similar, which comprises of the following:

  • Table: main part of the page. For most of the time, a simple table containing only text would work. But sometimes we need customized table, for example, to display user avatar and other information.
  • Filter: used to search or filter the content of the table. A search box is always needed to search certain information of the table.
  • Pager: together with filter to control the content of table.
  • Actions: adding or batch deleting the items in the table are also frequent, which usually appears at the top of the table.
  • Extras: tab navs, title, loading icon … elements that appear on almost every page

Here’s a very quick sketch of the page we are working on.

Example Page

We can imagine there is a lot of interactions between these components, especially around the table. And a lot of these are very tedious, for example, the manipulation of the pager. However, these functions are quite similar despite the content of the table.

Instead of manually combining these components every time, it’s a very good example of when to use a HOC.

Imagine now, we’re on the position to create such a HOC. What should we consider? Let’s name this HOC createPage.

Input

What should be the input of our function? Obviously there should be a big object be passed in to control all the parameters. Let’s call this big object schema.

createPage(schema)

As we considered before, although the main components is for most of the time table, we should also allow the case of other table-like components. So we could enable other options by adding a second component.

createPage(schema, tableLikeComponent)

It’s a personal preference of whether to put the second argument into the schema. Let’s put it outside for now.

For the detailed content of our schema, we’ll discuss later. We can add some basic fields at the moment.

// schema 
{
    title: 'some title',
    pageClass: 'some-class',
    tabs: ['tab1', 'tab2']
}

HOC inside HOC

Clearly, there is still a lot to do inside each sub-component, such as table and filter. We should keep these separated as a single component, rather than control the whole logic in our createPage function. So, corresponding to the hierachical structure, we should keep separate fields in our schema.

// schema
{
    table: {
        columns: [],
        ...
    },
    filter: {
        type: 'select'
    },
    ...
}

When handling the internal logic of our page, we just pass the whole field into the sub Table or filter components. And let the component itself to handle the arguments. This will separate concerns for each component. Also, we could update the version of sub-component apart from the page. We should keep the component as single functional as possible. The createPage should just handle the logic in the page level.

Extensibility

When designing component, one thing that should always be kept in mind is extensibility. There is a balance between extensibility and easy-to-use. More DIY space means the component is harder to use.

Event hooks

Firstly, hooks should be provided at important timings. The detailed mechanism for this to work depends on the framework. In San, we have a ‘message’ mechanism, which enables us to fire certain events to allow communication between parent and child elements. For example, we could fire a ‘refresh’ event when we change the filter values and refresh the table.

In React, such mechanism is not provided. Instead we could pass some hook functions, for example, onRefresh() and onFilterChange(), which is just like the React lifecycle methods. Or functions like beforeRefresh() to expose the internal request parameters and give space for further customization.

Child Components

Apart from the hooks, we should also enable the possibility of optional child components. An often case is that we want to embed some custom components in the last column, when we want to put some buttons or progress bar. In San, we achieve this by using slots. It works like the slot in Vue, which predefines some position in the parent html element for custom element.

The React component is more flexible, since it’s just a function. We could pass in some render function to render any component we like.

Summary

Programmers always tend to save time doing redundant things. Designing a HOC is just one of this. From my experience, HOC always comes from redundant work. When you need to do something for a lot of times, and 90% of the work is similar, consider using a HOC.

Some of the concerns of HOC is brought up here, while much of the concern is related to the page itself, or to the framework used. The popularity of React has brought the concept of HOC in front of every front end developer. Also this post will be updated now and then to address new issues arised.

Useful links

Many experienced developed have talked about the issues of HOC, including the React official documentation. I’ll just list some of them here.

React: Higher-Order Components

React Component Patterns

The famous post by Dan Abramov: Presentational and Container Components


EvsChen

Written by EvsChen.
A craftsman and a programmer.