Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

Ext JS to React: List

Published on February 8, 2018
Last Updated on April 8, 2021
Application Development

This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.

The Ext JS Grid component gets all the mentions. But, what about the lowly List? Yes, the List has quietly been taking care of our column-less data presentation needs for years now and maybe it should have a little spotlight, too. Grids and Lists share a common pedigree and their goal is largely common: display a dataset as items, often rows, that the user is able to interact with. Lists are simpler animals compared to the Grid. For the simpler use cases, that’s a good thing. In the following sections, we’ll show just how easy it is to author a List component using React. We’ll demonstrate how you can add functionality like selection and clickable disclosure icons separate from the List class. This allows your List to be as lean or as complete as your use case may dictate.

Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.

Writing a List

React, along with JSX and JavaScript itself, makes creating a simple list extremely easy. You may be surprised how little React code it takes to generate a list component using an array of data. For these examples we’ll use the following data set:

export default [{
    id: 1,
    firstName: 'Haydee',
    lastName: 'Fennell',
    company: 'NitroSystems',
    hireDate: '27/11/2002'
  }, {
    id: 2,
    firstName: 'Stan',
    lastName: 'Garling',
    company: 'Ulogica',
    hireDate: '13/05/2001'
  }, {
    id: 3,
    firstName: 'Gavin',
    lastName: 'Paquette',
    company: 'MediaDime',
    hireDate: '27/01/2008'
  }, {
    id: 4,
    firstName: 'Vernon',
    lastName: 'Drolet',
    company: 'Qualcore',
    hireDate: '04/05/1998'
  }, {
    id: 5,
    firstName: 'Marcus',
    lastName: 'Brier',
    company: 'GrafixMedia',
    hireDate: '30/08/1995'
  }, {
    id: 6,
    firstName: 'Haley',
    lastName: 'Pullman',
    company: 'Eluxa',
    hireDate: '05/06/2007'
  }, {
    id: 7,
    firstName: 'Raylene',
    lastName: 'Seal',
    company: 'OpenServ',
    hireDate: '28/12/2005'
  }, {
    id: 8,
    firstName: 'Dannielle',
    lastName: 'Sager',
    company: 'Infratouch',
    hireDate: '18/12/1997'
  }, {
    id: 9,
    firstName: 'Madelyn',
    lastName: 'Sprowl',
    company: 'Hivemind',
    hireDate: '09/12/1992'
  }, {
    id: 10,
    firstName: 'Drew',
    lastName: 'Hollis',
    company: 'Hivemind',
    hireDate: '01/07/1993'
  }];

React ListItem class

Our list view is essentially a div with child rows created using an array of data objects. Let’s first define the ListItem class that we’ll use in each of our list examples:

import React from 'react';
import './ListItem.css';

const ListItem = (props) => {
  const { children, className = '', company, firstName,
        hireDate, id, lastName, onClick, toolPosition } = props;
  const toolCls = toolPosition === 'left' ? 'item-tools-left' : '';

  return (
    <div
      className={`list-item ${className} ${toolCls}`}
      onClick={onClick}
      data-id={id}
    >
      <div className="body">
        <div className="main">{lastName}, {firstName}</div>
        <div className="secondary">
          {company}
          <span className="meta"> (hired: {hireDate})</span>
        </div>
      </div>

      <div className="tools">{children}</div>
    </div>
  );
};

export default ListItem;

ListItem explained

The ListItem class is a container with two direct child items. The first child item (decorated with the "body" class name) contains the body content of our list items as a template consuming the data object passed to it via props. In this example we’re outputting a few properties (name, etc.), but in a real world application we could display any number of properties with any number of custom React components. The <div className="body"> portion of the ListItem can be substituted with other components with their own HTML element structure and style rules.

Example List Body class

For example, we could define the following class:

const AltEmployee = ({ company, firstName, lastName }) => (
    <div className="body">{firstName} {lastName} (<i>{company}</i>)</div>
);

It could then be composed into ListItem as needed:

// … from the above example
<div className="body">
  <div className="main">{lastName}, {firstName}</div>
  <div className="secondary">
    {company}
    <span className="meta"> (hired: {hireDate})</span>
  </div>
</div>

// … could be replaced with the following
<AltEmployee firstName={firstName} lastName={lastName} company={company}/>

After the "body" element we see the second child item, a div with the class name of "tools". It will render any children passed to the ListItem. We’ll look at how to pass in child items similar to the disclosure buttons seen in Ext JS / Sencha Touch a bit further down the article. For now, we’ll just set up the structure allowing any child items we choose to be composed within the ListItem.

List.css

Here are the CSS styles we’ll use for the ListItem’s contents, including any child items. We make use of CSS flexbox (IE11+) to manage the layout of the ListItem‘s child elements, including the ability to reverse the child items’ order by passing the prop of toolPosition="left" to a ListItem to arrange the "tools" element before the "body" element.

.list-item {
  border-bottom: 1px solid #eaeaea;
  padding: 5px;
  cursor: pointer;
  user-select: none;
  display: flex;
}
.list-item.item-tools-left {
  flex-direction: row-reverse;
}
.list-item.item-tools-left .tools {
  margin-right: 12px;
}
.list-item .body {
  flex: 1;
}
.list-item .main {
  font-weight: bold;
}
.list-item .meta {
  color: #a5adaf;
}
.list-item .tools {
  display: flex;
  align-items: center;
  justify-content: center;
}
.list-item .tools > * {
  margin-left: 8px;
}

Simple list example

Let’s create a simple list using the ListItem class and our data set:

import React from 'react';
import data from './data';
import Selectable from './Selectable';
import ListItem from './ListItem';
import Tool from './Tool';

function handleItemClick (e) {
  console.log('item clicked');
}

const App = () => (
  <div style={{width: '400px'}}>
      {data.map(item =>
        <ListItem key={item.id} onClick={handleItemClick} {...item}/>
      )}
  </div>
);

export default App;

In this example we keep the list pretty simple letting the ListItem JSX and its accompanying CSS do the heavy lifting. An item-click event handler can be created and passed in using the onClick prop.

Ext JS to React: List, Simple

List selection

At this point, our List implementation is only as complex as it needs to be. So, how can we add functionality to our list to be used on an as-needed basis? In the Mixins article of this series, we looked at a couple of common patterns that can be used to share code between classes. Not only does this promote the DRY principle, it ensures a consistent selection behavior for our users. One of those patterns is referred to as “render props” and is exactly the method we can use to enhance our list.

Selectable class

Let’s take a look at the Selectable class we’ll use to enable ListItem selection:

import React, { Component } from 'react';
import './Selectable.css';

class Selectable extends Component {
  static defaultProps = {
    itemSelector: '.list-item'
  }

  state = {
    selected: []
  }

  handleClick = (e) => {
    const item = this.getItemFromEvent(e);

    if (!item) { // if the click starts on one item and ends on another
      return;
    }

    const selectedId = parseInt(item.getAttribute('data-id'), 10);
    const { data, selection, onSelection } = this.props;

    let { selected } = this.state;

    let add = true;

    if (selection === 'multi') {
      const last = selected[ selected.length - 1 ];

      if (last) {
        const lastIdx = data.findIndex(item => item.id === last);
        const currentIdx = data.findIndex(item => item.id === selectedId);

        if (lastIdx === currentIdx) {
          // indices are the same, deselect
          selected.splice(currentIdx - 1, 1);

          add = false;
        }

        if (e.shiftKey) {
          if (lastIdx !== -1 && currentIdx !== -1) {
            // get all items between the last selected item
            // and the current clicked item
            if (lastIdx < currentIdx) {
              for (let i = lastIdx + 1; i < currentIdx; i++) {
                selected.push(data[ i ].id);
              }
            } else {
              for (let i = lastIdx - 1; i > currentIdx; i--) {
                selected.push(data[ i ].id);
              }
            }
          }
        } else if (!e.ctrlKey) {
          // shift or ctrl keys were not pressed, need to clear out
          // so only the item being clicked on is selected
          selected.length = 0;
        }
      }
    } else {
      // single mode, clear out the array
      selected.length = 0;
    }

    if (add) {
      selected.push(selectedId);
    }

    if (selected.length > 1) {
      // remove duplicates
      selected = [ ...new Set(selected) ];
    }

    this.setState({
      selected
    });

    if (typeof onSelection === 'function') {
      onSelection(selected.map(id => data.find(item => item.id === id)));
    }
  }

  getItemFromEvent (e) {
    let el = e.target;
    let matcher = el.matches ? 'matches' : 'msMatchesSelector';
    let selector = this.props.itemSelector;

    while (!el[matcher](selector) && (el = el.parentElement));
    return el;
  }

  getSelectedCls = (id) => {
    return this.state.selected.includes(id) ? 'list-item-selected' : '';
  }

  render () {
    const { getSelectedCls } = this;

    return (
      <div className="selection-wrap" onClick={this.handleClick}>
        {this.props.render({ getSelectedCls })}
      </div>
    );
  }
}

export default Selectable;

Our Selectable class is comprised of 4 class members:

  • state: The state object’s selected item is updated by the handleClick method as the user clicks on ListItems. It includes the ID’s from the data backing each selected ListItem.
  • handleClick: This method coordinates the adding / removing of ID’s to the Selectable instance’s state as the user clicks on ListItems. It responds to shift / ctrl key presses for each user click to coordinate the selected array appropriately.
  • getItemFromEvent: A helper method for handleClick used to fetch the ListItem‘s wrapping element no matter which element / descendant element is clicked within the ListItem.
  • getSelectedCls: This method is passed to the render prop function used to render all child items. It’s called by the child items to determine whether a selected class name is to be applied to each item as it’s rendered. This allows the selection logic / decoration responsibility to be managed by the Selectable class, not any of the components / elements rendered within its render prop function (we’ll see it in action in the example to follow).

Selectable CSS

Below is the styling for the selected ListItems:

.list-item-selected {
  background-color: #eaeaea;
}

Selectable list example

Here is how we can use our Selectable component within the composition of our previous list example with only a couple of modifications:

import React from 'react';
import data from './data';
import ListItem from './ListItem';
import Selectable from './Selectable';

function handleItemClick (e) {
  console.log('item clicked');
}

function handleSelectionChange (selected) {
  console.log(`selection count: ${selected.length}`);
}

const App = () => (
  <div style={{width: '400px'}}>
    <Selectable
      selection="multi"
      data={data}
      onSelectionChange={handleSelectionChange}
      render={({ getSelectedCls }) => {
        return data.map(item => {
          const { id } = item;

          return (
            <ListItem
              key={id}
              onClick={handleItemClick}
              className={getSelectedCls(id)}
              {...item}
            />
          );
        });
      }
    }/>
  </div>
);

export default App;

In this example, we left the handleItemClick click handler and introduced another handler as well, handleSelectionChange. Each will be called during user interactions with the list. The handleItemClick handler receives the click event as a parameter while handleSelectionChange receives the array of IDs for any selected items as the selection changes.

The Selectable component is composed within our “list” div in place of the items array from our first example. Its render prop function passes the getSelectedCls method belonging to the Selectable class. Each ListItem calls getSelectedCls to get an additional “selected” class for its wrapping element, but only if the Selectable instance has recorded that particular ListItem‘s ID as being “selected”.

To select more than one ListItem at a time, set Selectable‘s selection prop to "multi". Holding the shift and / or control (command) key while selecting items in the list grows the selection range / items respectively.

Ext JS to React: List, Selection

List tools

Finally, let’s include “disclosure” tools to the list items like we’ve seen in Sencha Touch and Ext JS. We defined our ListItems with a containing element for rendering any child items passed in. We’ll define a Tool class for this purpose specifically. It will make use of FontAwesome icons so first let’s install the FontAwesome packages we’ll use in our example:

npm install --save @fortawesome/fontawesome
npm install --save @fortawesome/react-fontawesome
npm install --save @fortawesome/fontawesome-free-solid

Note: You may find you need to run npm install once more after installing the FontAwesome packages.

Tool class

Now let’s define our Tool class:

import React from 'react';
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
import * as Icons from '@fortawesome/fontawesome-free-solid';

const Tool = ({ icon = 'ArrowRight', onClick }) => (
  <FontAwesomeIcon icon={Icons[`fa${icon}`]} onClick={onClick}/>
);

export default Tool;

Complete list example

With the Tool class defined, we can now pass Tools as child items of each ListItem. Let’s take a look at an example we built on our previous selection example allowing us to demonstrate a selectable list with Tools:

import React from 'react';
import data from './data';
import ListItem from './ListItem';
import Selectable from './Selectable';
import Tool from './Tool';

function handleItemClick (e) {
  console.log('item clicked');
}

function handleSelectionChange (selected) {
  console.log(`selection count: ${selected.length}`);
}

function handleToolClick (e) {
  e.stopPropagation();

  console.log('tool click');
}

const App = () => (
  <div style={{width: '400px'}}>
    <Selectable selection="multi" data={data} render={({ getSelectedCls }) => {
      return data.map(item => {
        const { id } = item;

      return (
        <ListItem
          key={id}
          onClick={handleItemClick}
          className={getSelectedCls(id)}
          {...item}
        >
          {item.company === "Hivemind" && <Tool icon="Archive" />}
          <Tool onClick={handleToolClick}/>
        </ListItem>
      );
      });
    }}/>
  </div>
);

export default App;

In this final example we bring it all together. Tools are displayed in the ListItems including one that is optionally displayed with the “Archive” icon when ListItem has a company value of “Hivemind”. We continue to call the ListItem click handler and the selection change handler and we’ve added a Tool click handler as well.

Ext JS to React: List, Tools

Conclusion

Enabling selection and triggering actions on a list of items is a common UI requirement. You can always use a pre-built library to handle this for you but this task is pretty simple using React alone. Allowing selecting and tools to be opt-in features using mixins can keep a list simple and lightweight, yet allow other lists the ability to add in the functionality when needed.

Posted in Application Development
Share this

Seth Lemmons

Seth Lemmons is a Senior Front End Engineer at Modus Create. Seth has devoted several years to learning Ext JS. He has spent the last 10 years building and supporting web applications with an eye for excellent user experience. Outside of work and a young family Seth has very little free time to just do what he wants. But, if he did have some extra time he'd kinda be into learning vector illustration. And someday he hopes to play video games again.
Follow

Related Posts

  • React Landing
    Ext JS to React: Migration to Open Source

    Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…

  • Ext JS to React: FAQ
    Ext JS to React: FAQ

    This is part of the Ext JS to React blog series. React is Facebook's breakout…

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
IT modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Our story
    • Careers
  • Let’s talk
  • EN
  • FR