ddraper

Deep Nesting React Components

Blog Post created by ddraper Employee on Jan 24, 2017
This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

Introduction

In a previous blog post I described one of the major challenges involved in providing re-usable components, where those components could be deeply nested (without the need to update the dependencies listed for their parents). The post also described a way that you could combine Surf, Aikau and Vue.js to solve the problem. I've since spent a couple of days experimenting with React and have discovered that it is also well equipped to solve this problem.

 

The solution is to make use of the "composition" capabilities provided by React. I'd already built a small prototype client for testing some of the new V1 REST APIs and realised that most of the components that I had created should be highly re-usable.

 

Starting Point

The prototype client called the "people" REST APIs to allow you to display the users registered on the Alfresco Repository. One of the best things about the V1 REST APIs is that the response schema is consistent across all the APIs and this means that the same Collection component can be used to retrieve data from any of the URLs that return multiple items (users, nodes, comments, favourite sites, etc). 

 

I've got into the habit of building UIs using the presentation and container components pattern described in this blog post (and not just for React, but for any UI framework). So whilst my outer Collection container component could easily be configured to use a different URL, the presentation components that it used needed to be swapped out according to the data being retrieved (i.e. the user view I had created would be no use for rendering node data).

 

My initial implementation (which you can test by checking out this tag) had the main Collection component (which was called List at the time) explicitly declared dependencies on the sub-components it wanted to use and then referenced them in the render function, like this:

 

List Component:

render() {
   return (
      <div ref="list" >
         <Toolbar list={this.state.list}
                  pageBackHandler={this.pageBack}
                  pageForwardHandler={this.pageForward}></Toolbar>
         <Filter />
         <ListView list={this.state.list}
                   navigationHandler={this.navigate}
                   orderBy={this.state.orderBy}
                   orderDirection={this.state.orderDirection}></ListView>
      </div>)
}

 

Single Level Nesting

The first step was to change the allow sub-components to be provided by the "children" props:

 

Collection Component (renamed from List):

render() {
   const childrenWithProps = React.Children.map(this.props.children, (child) => React.cloneElement(child, {
      list: this.state.list,
      orderBy: this.state.orderBy,
      orderDirection: this.state.orderDirection,
      relativePath: this.state.relativePath
   }));
   return (
      <div ref="list" >
         {childrenWithProps}
      </div>)
}

 

Note that it was necessary to pass any relevant state properties into any children provided (which I discovered via this Stack Overflow question). This then made it possible to use my Collection component to build my user list display like this:

 

Home Component:

<Collection skipCount={0}
            maxItems={10}
            orderBy="firstName"
            orderDirection="DESC">

   <CreateUserButton/>
   <Filter />
   <UserTableView />
</Collection>

 

Notice that this enabled me to configure relative sorting and pagination properties (mapped directly to the REST API parameters) as attributes of Collection. In this example the CreateUserButton, Filter and UserTableView are custom components passed as children to the Collection component.

 

Deep Nesting

The majority of the UserTableView component was also ripe for reuse so I further abstracted that to a TableView component that would accept child components as properties for the header, body and footer.

 

Because this component had 3 different children properties it is necessary to use a slightly more complex syntax but the Collection component could then nest an abstract TableView with nested TableHeading, TableCell and Pagination sub-components configured to display metadata relevant to the data being displayed. Therefore a node list display can be defined like this:

 

Home Component:

<Collection url="/api/-default-/public/alfresco/versions/1/nodes/-root-/children" 
            orderBy="name">

   <BreadcrumbTrail/>
   <TableView
      headerChildren={
         [<TableHeading label="Name"
                        orderById="name" />
,
          <TableHeading label="Created By"
                        orderById="createdByUser.displayName" />
]
      }
      bodyChildren={
         [<TableCell property="name" navigation={true}/>,
          <TableCell property="createdByUser.displayName" />]
      }
      footerChildren={
         <Pagination colspan="2"/>
      }
   >
   </TableView>
</Collection>

 

Notice that the CreateUserButton and Filter sub-components have been swapped out for a BreadcrumbTrail with no issues and that the url for retrieving the data has been configured for accessing nodes for the Repository root location.

 

Further Refinements

Although the multiple children property approach was working I didn't like the way the syntax looked. So I decided to further abstract the TableView to create separate components for TableViewHead, TableViewBody and TableViewFoot.

 

Making use of these gives a much cleaner, standard HTML appearance:

 

Home Component:

<Collection url="/api/-default-/public/alfresco/versions/1/nodes/-root-/children" 
            orderBy="name">

  
   <BreadcrumbTrail/>
  
   <TableView>

      <TableViewHead>
         <TableHeading label="Name" orderById="name" />
         <TableHeading label="Created By" orderById="createdByUser.displayName" />
      </TableViewHead>

      <TableViewBody>
         <TableCell property="name" navigation={true} />
         <TableCell property="createdByUser.displayName" />
      </TableViewBody>
     
      <TableViewFoot>
         <Pagination colspan="2"/>
      </TableViewFoot>

   </TableView>
</Collection>

 

Try It Out

To test this out you can check out this tag from this GitHub repository then build and start the application by running:

npm install
npm start

...and open a browser at http://localhost:3000/home (make sure that you have a local Alfresco Repository running that provides the V1 REST APIs such as a recent 5.2 Community release).

 

This is a screenshot of what you should see:

 

Summary

I think that this is an excellent example of how the consistent response schema in the V1 REST APIs is going to significantly improve custom user interface development for Alfresco. It's also an interesting exploration of one of the capabilities that React is able to offer that none of the other UI libraries/frameworks I've experimented with has been able to provide out-of-the-box.

 

In my opinion I think it would be very fast to build out applications using this approach but I'd be interested to know other peoples thoughts on it!

Outcomes