Displaying a List of Items in React, but Composed

Displaying a list of items is a challenge you will encounter in most web applications. When using a view layer library such as React, you only have to iterate over the list of items and return elements. However, often you want a couple of more features such as filtering, sorting or pagination. Not every list component needs it though, but it would be great to have these functionalities as opt-in features whenever displaying a list of items.

We are excited to open source our in-house solution at Small Improvements which handles this use case: react-redux-composable-list. In our web application, customers often need to work with lists of data while managing their feedback, objectives or other content in our system. At Small Improvements our customers range from an active user count of 20 to 2000 users. Thus it can happen that we need to display a lot of data yet have to keep it accessible for people managing it.

giphy

The requirements for each list of data are different. One list is just fine with a filter functionality. Another list mixes together selectable and filterable items. Each displayed list has different requirements. The library we are open sourcing solves all the requirements we had in-house at Small Improvements. The library is highly extendable and builds up on composition. You can come up with your own opt-in features.

Demo and Features

The react-redux-composable-list comes with the following features:

  • Filtering (AND filter, OR filter, multiple filters)
  • Selecting
  • Sorting
  • Magic Column (collapsing multiple columns in one column)
  • Pagination

There are two demo applications up and running to show the features of react-redux-composable-list.

While the former demonstrates all features in one real world example, the latter separates the examples by feature.

The Real World example shows that all features can be used altogether by composing them. To specify the opt-in features of your list components you use React’s higher order components.

const List = ({ list, stateKey }) => {
  ...
}

...

const EmptyBecauseFilter = () =>
  <div>
    <h3>No Filter Result</h3>
    <p>Sorry, there was no item matching your filter.</p>
  </div>

export default compose(
  withEmpty({ component: EmptyBecauseNoList }),
  withSelectables({ ids: [0] }),
  withPreselectables({ ids: [2, 3] }),
  withUnselectables({ ids: [4, 6] }),
  withFilter(),
  withEmpty({ component: EmptyBecauseFilter }),
  withSort(),
  withPaginate({ size: 10 }),
)(List);

You can find the implementation of both demo applications in the official GitHub repository. Further details about specific features can be found in the official documentation.

Getting Started

If you want to jump right into using the library, you should checkout the Getting Started section in the official documentation.

For instance having a list of items with the option to select items can be accomplished with the following component:

import { components, enhancements } from 'react-redux-composable-list';
const { Enhanced, Row, Cell } = components;
const { withSelectables } = enhancements;

const ListComponent = ({ list, stateKey }) =>
  <Enhanced stateKey={stateKey}>
    {list.map(item =>
      <Row key={item.id}>
        <Cell style={{ width: '70%' }}>{item.title}</Cell>
        <Cell style={{ width: '30%' }}>{item.comment}</Cell>
      </Row>
    )}
  </Enhanced>

export default withSelectables()(ListComponent);

Afterwards it can be simply used by passing a list of items and a state key to identify the table state.

import SelectableListComponent from path/to/ListComponent';

const list = [
  { id: '1', title: 'foo', comment: 'foo foo' },
  { id: '2', title: 'bar', comment: 'bar bar' },
];

const App = () =>
  <SelectableListComponent
    list={list}
    stateKey={'MY_SELECTABLE_LIST'}
  />

If you want to dive deeper into the library, you can checkout the whole documentation to learn more about the library and how to use it.

Extend it & Contribute

You can write your own enhancements and enhancers, because you have provide you with access to the library’s API. To be more specific, the library API is nothing more than action creators and selectors for the Redux store. All the state that is managed for the tables is organized in a Redux store. You will find everything you need to know about the API in each documented feature. In general, the documentation is a good place to get started and to read up everything about all the features.

We would love, if you would give it a shot and give us feedback about it. In addition, we welcome you to make contributions to the library.

Resources

Our journey migrating 100k lines of code from AngularJS to React (Chapter 1)

Web

Intro

This is the first post of a series explaining the story and technical learnings we had from starting to migrate from AngularJS to React. Check out the github repo for examples and the full code.

Our frontend story so far

At Small Improvements we’re aiming to make meaningful feedback available for every employee in every organisation. This also implies that we provide the best experience for our users. Therefore we’ve been on the front line to adopt AngularJS over Wicket, and started to rewrite our core features in AngularJS back in 2012. We saw the great potential in having a dynamic single page application.

In 2014, when the Angular team announced Angular 2, we already had a very large application and had gained a whole lot of knowledge using Angular. We were worried and excited at the same time. We faced a lot of challenges scaling Angular 1 and implementing best practices while moving fast.

In 2015 we sent almost all developers to AngularConnect in London, expecting the Angular 2 BETA release. Two of our developers gave a talk to share our approach to and learnings from writing a huge AngularJS application. We came back with the impression that Angular 2 was still very unstable and no clear migration strategy seemed to be available.

anguarconnenct.jpg
The Small Improvements Team in London at AngularConnect

Testing React plus Relay and GraphQL in the field

Our CEO has a strong engineering background, so he’s very open to play around with new technology and loves hackathons and ship-it weeks. That’s why he was very open to giving React (with Relay and GraphQL) a chance. As a company, our approach to evaluating a new technology is to have one of our dev teams make an initial tech spike. In this case Team Green decided to experiment with the novel technology in the field by coding a prototype for a new feature in the new tech stack.  We found React extremely promising and it solved a lot of challenges we had with Angular 1. Relay was cool, but lacking some core features at that time, such as support for invalidation or lazy loading of expensive fields.

Also adopting Relay would mean a complete buy-in from our whole stack, Frontend and API layer, due to the dependency on GraphQL.

So to sum it up, the outcome was: React: OMG!, Relay: Cool, but…

Our Reasons to go with React

  • Easy to write: It’s closer to vanilla JavaScript and components come without any boilerplate configuration
  • Great use for atomic components. In contrary to AngularJS where every scope is “expensive”
  • Easier to understand, React is the view library and has a slim API
  • Designed with performance in mind: Concept of virtual DOM
  • Attractive for recruiting, new technology attracts passionate developers, because they are keen to adopt new technologies
  • New challenges for the dev team, learn and grow!

When we used it for a large feature – a new Activity Stream – it multiplied our investment, due to an unclear focus. We were shifting between trying out the new technology and building first iterations on the feature.

Lessons learned

Use a smaller feature as playground when experimenting with a new technology.

The migration strategy

Now that we’ve decided to move from AngularJS to React, we saw two options for a migration strategy: A complete rewrite of our frontend or a slow transition. Let me rephrase that: We saw one option: A smooth and focused transition. Nobody wanted to spend months rewriting our whole application, although that would have been a fun argument with our CEO. At Small Improvements, we have a strong customer centred culture, so we didn’t want to slow down too much on our mission. Additionally it is a high risk to rewrite everything with a technology that nobody is experienced with.

Each week all Software Developers at Small Improvements meet for a developer exchange meeting. That’s the place where we share learnings, discuss ideas but also decide on larger undertakings. In this case we discussed and decided on the idea for the migration strategy a sub team of developers has developed and presented.

The basic idea

A frontend application is built like a tree, since HTML documents imply a structure of nested HTML elements. Modern web applications are structured in nested components. A simplified mock of an application displaying a list of comments may look like that:

Screen Shot 2017-01-24 at 09.50.02.png

The corresponding component tree looks like this:

Screen Shot 2017-01-24 at 09.50.06.pngWe looked at how complex it would be to replace and rewrite this tree.

screen-shot-2017-01-24-at-09-50-34

The main Application component is hard, it usually is wired up with complex logic like routing. Similar the Navigation component. The routing is tightly coupled with the main components and in case of AngularJS central piece of the framework. A NavItem is easier, it displays a link and has some trivial logic like “am I active” and displays a link with text. The content part of our app consists of a sub tree displaying a list of comments. The ComponentList is trickier, since it is hooked to the data layer and may contain state like: what item is selected etc. Again we see the Comment is the easiest part of that tree, basically rendering a Comment and handling user interaction. The Text component for instance is simply responsible for rendering the text. That component is easiest to re-write in another technology.

Our conclusion was that the further down the component tree you go, the easier it gets to replace components. With that in mind, we defined guidelines and looked at requirements for that migration strategy.

Guidelines

How to tackle new features?

We wanted a full buy in, so we defined our first guideline:

  1. Every new feature will be built in React & Redux.

How to tackle existing code?

  1. If possible start to migrate leaf-first up to a whole component tree until you hit the routing module.
  2. If you touch old code/ components, estimate how much it would cost to rewrite it, if less than 30 minutes, rewrite, else get a second opinion.

How to migrate common UI components?

The basic building blocks of an application are generic, reusable UI components, like Dropdowns, Buttons, Forms etc. Those are necessary to build new components with React.

  1. Re-write generic UI components when you need them, and let other devs know that they now exist. Use that chance to improve the design/ UX.

Requirements

  • Component based architecture
  • Angular Directives structured as container/ presenter components, read more here
  • Separation of concerns/ View/ Logic/ Service/ Communication layers and Injectable actions to encapsulate side effects like http calls etc

Fortunately our frontend design already fulfilled the requirements. If you want more information on how to design and structure your application watch our talk How to design large AngularJS applications that scale from AngularConnect or Refactoring To Components by Tero Parviainen.

Building bridges

We found that it was easiest to start by replacing the leaves of our application component tree. The missing piece was a bridge between the “old” world and the “new” world. Meaning AngularJS and React, in our case. How can we use React to render the Text component and get it’s data from an AngularJS component?

Rendering React within AngularJS

A React component is, well, just another UI component. It gets data and actions via props and is rendered to the DOM. It is responsible for internal state and handles user interaction. So a simple concept of our bridge could be an AngularJS component working as thin layer with the responsibility to pass on data to the React Component.

Let’s aim to answer our first uncertainty: Can we use an AngularJS component to render a React component?

This is our AngularJS comment component:

module.exports = angular.module('ngReactExample.comment', [
]).component('comment', {
    bindings: {
        comment: '<',
    },
    template: '{{ $ctrl.comment.text }}',
    controller: function() {
    }
});

Our React version of a comment looks like that

const Comment = (props) => {
    return (
         { props.comment.text }
    );
};
export default Comment;

The React component is rendered to the DOM by calling:

ReactDOM.render(<Comment />, element);

Let’s try to call this within an AngularJS component:

import Comment from './Comment';

module.exports = angular.module('ngReactExample.comment', [
]).component('comment', {
    bindings: {
        comment: '<',
    },
    controller: function() {
        ReactDOM.render(<Comment />, $element[0]);
    }
});

It works! This is the simple yet powerful starting point from where we can now build our AngularJS – React bridge. The elegant part is that we don’t need to mess around with DOM node ids or use the DOM API to query the element we want to render React to. We can directly pass the reference to the AngularJS element. You might have noticed a little detail – at the moment we’re only rendering the React component when this component is initialized. In a dynamic app we want dynamic components. So we want to trigger the rendering whenever the component changes. To achieve this we can use the lifecycle method $onChanges.

import Comment from './Comment';

const render = (element) => {
    ReactDOM.render(
        <Comment />,
        element
    );
}

module.exports = angular.module('ngReactExample.comment', [
]).component('comment', {
    bindings: {
        comment: '<',
    },
    controller: function($element) {
        const $ctrl = this;
        $ctrl.$onChanges = () => render($element[0]);
    }
});

Now whenever our AngularJS component receives changes we’re redrawing the React component.

With this working we can tackle the next question: How we can pass data down to our React component?

Passing data from AngularJS to React

In React we use props as interface to pass data to a component. An AngularJS directive receives inputs via bindings, so we will get the comment data from an outside component and pass it down to our React component. The full working bridge looks like this:

import Comment from './presenter';

const render = (element, props) => {
    ReactDOM.render(
        <Comment { ...props } />,
        element
    );
}

module.exports = angular.module('ngReactExample.comment', [
]).component('comment', {
    bindings: {
        comment: '<',
    },
    controller: function($element) {
        const $ctrl = this;
        $ctrl.$onChanges = () => render($element[0], { comment: $ctrl.comment });
    }
});

Fixing the possible memory leak

As described here React will not automatically clean up the components which can lead to a memory leak. We can use the lifecycle hook $onDestroy() of our AngularJS component to unmount the React component.

import Comment from './presenter';

const render = (element, props) => {
    ReactDOM.render(
        <Comment { ...props } />,
        element
    );
}

module.exports = angular.module('ngReactExample.comment', [
]).component('comment', {
    bindings: {
        comment: '<',
    },
    controller: function($element) {
        const $ctrl = this;
        $ctrl.$onChanges = () => render($element[0], { comment: $ctrl.comment });
        $ctrl.$onDestroy = () => ReactDOM.unmountComponentAtNode($element[0]);
    }
});

Voila! We’ve successfully passed data from AngularJS to a React component.

Completing the bridge from AngularJS to React

We’ve now found a way to wrap a React component with an AngularJS layer, so we can hook it up to the rest of our application.

This is a great starting point and a good proof of concept. Our current bridge is an interesting evolution of this first spark. In the next posts we will go more into technical details, also answering the question what we do, when the AngularJS component get’s destroyed, and more topics.

To be continued…

A sneak peak into the next chapter where we’ll have a closer look at:

  • Using AngularJS services in React
  • Improving the AngularJS-React bridge to work with Hot Reloading and avoid unnecessary re-renderings
  • Rendering AngularJS components in React

Stay tuned! 😉

Thanks for reading and if you have any questions or feedback, don’t be shy and reach out! @sfroestl If you liked the post, please share!

About the author

green3-1024x683

Sebastian Fröstl

Team Lead. Software Engineer. Trainer. Coach. Speaker. Devoted to Personal Development. Organizer of @angular_berlin.
@sfroestl
sebastianfroestl.de

Resources