A new way of writing components: Compound Components

As you already might have used many patterns/techniques while developing an application with React, today we are going to scratch the surface of one of the techniques called Compound Components.

Why Compound Component?

Compound Component allows Developer to take control over the rendering behavior, that gives the Developer, ability to decide in what order the component should render. Compound Component also reduces the tension of Developer by not needing to passing tons of configuration as a prop. For instance.

Giving more rendering control to Developer
The main purpose of creating this Compound Component was not to tie rendering to the Tabular Component, but instead put the developer in charge of it.

Compound Component is a great pattern that has proven to be very valuable for several React libraries. In this post, we will discuss how to use Compound Components to keep your React application tidy, well-structured and easy to maintain

This post assumes basic knowledge of

  1. ES6
  2. React Context

What is Context?

Context is a great feature introduced in React and is very useful when you want to expose the APIs, so application using your component can make use of those API. Context is also used to pass the data deep in the component tree without needing intermediate component know about it. Context provides an abstraction, handling the dirty work at one place, so the component using it does not need to know how it’s done.

Context provides great flexibility to Compound Component. With the help context, the Developer gets more control over how he wants to render the components. We will see the benefits of Context throughout this post.

You can learn more about context here– https://reactjs.org/docs/context.html

Use Case
1. As a user, I want to display the information in a tabular form
2. As a user, I want to have a search functionality built in.
3. As a user, I want to have a control over which fields the search should work.
4. Whatever keywords user enter, filter the records based on keywords, and highlight those keywords.

Let’s dive in!

So our first task is to create a UI which will render data in tabular form. Let’s create a file named table.js and set up the foundation.

table.js

Table.js

So what is happening in this file?We have created a Parent Component which will be responsible for passing Props to Child Components…How?? that will see in a minute.

Now let’s define the data that we are going to pass as Props to our Tabular Component

const columns = [
  {
    displayName: "Id",
    sortable: true,
    searchable: true
  },
  {
    displayName: "FirstName",
    sortable: true,
    searchable: true
  },
  {
    displayName: "LastName",
    sortable: true,
    searchable: true
  }
];

This is the metadata for displaying the column name and also tells the Tabular Component which field should be searchable. Remember use-case 3. And this is the data which will be rendered into Rows

const data = [
  {
    Id: "1",
    FirstName: "Luke",
    LastName: "Skywalker"
  },
  {
    Id: "2",
    FirstName: "Darth",
    LastName: "Vader"
  },
  {
    Id: "3",
    FirstName: "Leia",
    LastName: "Organa"
  }];

Now, let’s define how our application is going to consume our Tabular Component.

app.js

app.js

As you can see, I have monkey patched the Table Component. For this to work, we need to have the reference to Table Component in Tabular Component like this:

Monkey Patching Table Component

Next up, we will define a Table Component, which will be responsible for drawing a table.

Table Component

Table component has access to the context defined in Tabular Component via

static contextTypes = {
  [TABULAR_CONTEXT]: PropTypes.object.isRequired
};

We are storing the instance of Table component in Context so can access its state from other components. Just to keep the code clean and readable, I have created a separate stateless Row Component for rendering data.

Stateless Row Component

So now it’s time to run the application…As you can see it renders

Under construction!!

Because we are not rendering the children of the Tabular component. Let’s quickly do that

Tabular Component

Here we are looping through all the children using React’s Children API and passing props to each child. So after updating the Tabular Component’s render method you should be able to see the desired result like this

Now we have completed our first use-case, lets quickly jump to second use-case which is —

Adding search functionality

For implementing search functionality, we need an input box. So for that, we will be creating a new component called SearchBox. Before that let’s update the app.js file

app.js

app.js

As you can see.how easy it is to maintain the code using Compound Component. We have added SearchBox in the same manner how we added Table Component.

SearchBox Component

SearchBox Component

This is a simple component not having much responsibility apart from rendering input box, and in this component also we are saving the instance of the component in Tabular Context.

 

One thing to note here is, we are not defining any state of an input field in SearchBox component because what we want is whenever user search for any query, we want to re-render the Table component with filtered records. If we persist the state of an input field in SearchBox component, only the SearchBox component will re-render and not the Table component.

We are saving the state of an input field in Table component to re-render, whenever input is changed. So what we are doing here is, we are accessing the state of Table component via context

this.context[TABULAR_CONTEXT].table

and updating it. So let’s make the necessary changes and add the filter logic in the Table component.

TableComponent

In the render method, before rendering Row, we are passing data through the search filter so every time user searches anything, we only render filtered data. So let’s see the what we are doing in the searchFilter method.

The searchfilter method takes row as input, then it fetches the value from row by using the column displayName. Second, we have the search value(query), we compare the searchValue with the value we got from row along with whether that field is searchable or not. After stitching this logic in Table Component, let’s see this in action.

Search filter in Action

With this, we have completed use-case 2 and use-case 3. Now, let’s add the functionality to highlight searched keywords. We want our component to be as customizable as possible. Hence we allow the user to add the style for highlighting of the keywords. Lets update the app.js file

app.js

Use-case4 Highlighting the searched keywords in Table

To highlight the string we need to update the Row component, as that is the one which is responsible for rendering rows.

Stateless Row Component

The only new thing in this is we are making use of query and highlightStyle, which are being passed as props. Also, we are calling highlightWord() method which returns the decorated string.

Highlighing the searched keyword

The highlightword method accepts actual column value, the query string, and the highlight style. We are extracting the string which is matched with query string and the actual value and wrapping it with a <span/> tag with the provided style and returning it to Row component.

Now, let’s run the code…

Highlighting searched query

Now if we look at the code that is being used by the application is very small, tidy and customizable.

Making rendering more powerful with React Context

Now, what if the Developer comes in and decides to change the style of Tabular component like this,

Just by adding the wrapping </div >, our UI breaks. Because if we see the render method of Tabular Component, we see it’s mapping the props over to its direct children, and now one of those children is a </div>. So we are cloning a Div and passing some stuff which is completely irrelevant to it.

Now, here comes Context to resue to decouple the UI hierarchy from the relation between Tabular and Table Component. The only thing we need to change in our app is, instead of taking data from props, we are going to make use of Context.

Tabular Component

Storing data in Context

Here we have updated the childContextTypes object and getChildContext() method to pass row data and column metadata via context. And with help of context, we have removed the dirty cloning implementation to pass the data to child components. Now Tabular component render method just returns the children.

Now let’s update the Table Component

Table Component

Pulling data from Context instead of Props

Here we have updated the contextTypes object to fetch row data and column metadata via context. With this changes, our app runs as usual.

There is one more feature a Table must have, which is Sorting. I haven’t added in this..but feel free to implement that. If you find any difficulties in implementing then let me know in a comment box.
You can find the source code here — https://codesandbox.io/embed/6j0kn85nmr

Motivation
Ryan Florence – Compound Components — https://www.youtube.com/watch?v=hEGg-3pIHlE

Leave a Reply

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