Ext JS to React: Load, Sort and Filter Data with React

   JavaScript
Ext JS to React: Load, Sort and Filter Data with React

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.

In previous articles on List and Grid, we used local data exclusively. In this article, we are going to look at tackling some basic remote data loading in order to transition from Ext JS’ strong data package. There are many different means of loading remote data depending on your server. In this article we will focus on loading JSON data using the native Fetch API.

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.

Sample Remote Data

For the examples in this article, we’ll use the following data (located at {appRoot}/public/data.json for those following along with the starter app):

[{
  "id": 0,
  "name": "Vernon Dunham",
  "company": "Qualcore",
  "email": "vernon.dunham@qualcore.com"
}, {
  "id": 1,
  "name": "Dori Neal",
  "company": "Sunopia",
  "email": "dori.neal@sunopia.com"
}, {
  "id": 2,
  "name": "Rico Muldoon",
  "company": "Airconix",
  "email": "rico.muldoon@airconix.com"
}, {
  "id": 3,
  "name": "Jason Neal",
  "company": "Qualcore",
  "email": "jason.neal@qualcore.com"
}, {
  "id": 4,
  "name": "Rico Pettey",
  "company": "Thermolock",
  "email": "rico.pettey@thermolock.com"
}, {
  "id": 5,
  "name": "Raymond Seabury",
  "company": "Airconix",
  "email": "raymond.seabury@airconix.com"
}, {
  "id": 6,
  "name": "Dori Pettey",
  "company": "Hivemind",
  "email": "dori.pettey@hivemind.com"
}, {
  "id": 7,
  "name": "Vernon Neal",
  "company": "Qualcore",
  "email": "vernon.neal@qualcore.com"
}, {
  "id": 8,
  "name": "Jason Neal",
  "company": "Qualcore",
  "email": "jason.neal@qualcore.com"
}, {
  "id": 9,
  "name": "Vernon Muldoon",
  "company": "Airconix",
  "email": "vernon.muldoon@airconix.com"
}, {
  "id": 10,
  "name": "Vernon Seabury",
  "company": "Hivemind",
  "email": "vernon.seabury@hivemind.com"
}, {
  "id": 11,
  "name": "Dori Neal",
  "company": "Airconix",
  "email": "dori.neal@airconix.com"
}, {
  "id": 12,
  "name": "Raymond Pettey",
  "company": "Airconix",
  "email": "raymond.pettey@airconix.com"
}, {
  "id": 13,
  "name": "Rico Muldoon",
  "company": "Qualcore",
  "email": "rico.muldoon@qualcore.com"
}, {
  "id": 14,
  "name": "Vernon Seabury",
  "company": "Sunopia",
  "email": "vernon.seabury@sunopia.com"
}, {
  "id": 15,
  "name": "Rico Pettey",
  "company": "Airconix",
  "email": "rico.pettey@airconix.com"
}, {
  "id": 16,
  "name": "Jason Dunham",
  "company": "Sunopia",
  "email": "jason.dunham@sunopia.com"
}, {
  "id": 17,
  "name": "Vernon Neal",
  "company": "Qualcore",
  "email": "vernon.neal@qualcore.com"
}, {
  "id": 18,
  "name": "Jason Pettey",
  "company": "Thermolock",
  "email": "jason.pettey@thermolock.com"
}, {
  "id": 19,
  "name": "Vernon Dunham",
  "company": "Thermolock",
  "email": "vernon.dunham@thermolock.com"
}]

Fetching Remote Data

Since React doesn’t have a data store, you may be worried about creating one yourself. Ext JS’s data stores were powerful and flexible, however, they are very feature-heavy and may be overkill for simple views. The action of loading JSON data using the Fetch API is actually extremely simple. Let’s take a look at a simple list component that loads its data remotely. While the loading is occurring we’ll show loading text:

class List extends Component {
  state = {}

  componentDidMount () {
    fetch('/data.json')
      .then(res => res.json())
      .then(this.onLoad);
  }

  parseData (response) {
    return response.data;
  }

  onLoad = (data) => {
    this.setState({
      data: this.parseData(data)
    });
  }

  render () {
    const { data } = this.state;

    return data ?
      this.renderData(data) :
      this.renderLoading()
  }

  renderData (data) {
    if (data && data.length) {
      return (
        <div>
          {
            data.map(item => (
              <div key={item.id}>
                <a href={`mailto:${item.email}`}>{item.name}</a> {item.company}
              </div>
            ))
          }
        </div>
      );
    } else {
      return <div>No items found</div>
    }
  }

  renderLoading () {
    return <div>Loading...</div>
  }
}

List Class Explained

The most important piece here is the componentDidMount. This React lifecycle method is where you would kick off the data loading and here we set the data (nested in a data root property) to the list’s state. We execute the parseData method when setting the state, you’ll see why we do this in a little bit. In the render method, we check if data is present. If it is, we render the data in the renderData method. If there isn’t any data, then we render some loading text via the renderLoading method. The loading text here is a simple div node, but could be something more visual for the user like we showed in the Floating Components article.

This code is very straightforward and you can see how simple it was to implement loading remote data using the Fetch API and we didn’t need to use a data store like we would with Ext JS. This means performance should be higher and a production build would be smaller. Of course, if we needed to load something other than a response the Fetch API natively supports, we’d have to replace it with some proxy/reader setup (think SOAP, XML, etc.).

Remote Data Example

import React from 'react';
import List from './List'

export default () => <List />;


Ext JS to React: Load, Sort and Filter Data with React



You can review the sample code on the Git repo.

Data Sorting Utility Functions

In order to sort data locally, we can add our sorting code in the parseData method. Once again, we aren’t in an environment like Ext JS that has functionality built into data stores. So we have to rely on native JavaScript here and therefore use Array’s sort method. Since sorting is likely going to be used in multiple places around an application, we can create a utility file that will handle sorting in an abstract way. You can save a Sort.js file within the src/util/ directory (depending on your React app setup) with this abstract code:

const dirMap = {
  // greater-than
  gt: { asc: 1, desc: -1 },
  // less-than
  lt: { asc: -1, desc: 1 }
};

const doSort = (A, B, property, direction = 'ASC') => {
  const a = A[ property ];
  const b = B[ property ];

  if (a < b) {
    return dirMap.lt[ direction.toLowerCase() ];
  }
  if (a > b) {
    return dirMap.gt[ direction.toLowerCase() ];
  }
  return 0;
}

const createSorter = (...args) => {
  if (typeof args[0] === 'string') {
    args = [
      {
        direction: args[1],
        property: args[0]
      }
    ];
  }

  return (A, B) => {
    let ret = 0;

    args.some(sorter => {
      const { property, direction = 'ASC' } = sorter;
      const value = doSort(A, B, property, direction);

      if (value === 0) {
        // they are equal, continue to next sorter if any
        return false;
      } else {
        // they are different, stop at current sorter
        ret = value;

        return true;
      }
    })

    return ret;
  }
}

export { createSorter };

Sorted List Class

When we want to sort data, we can use the createSorter function and pass the generated function to the array’s sort function. The beginning of our List would then look like:

import React, { Component } from 'react';
import { createSorter } from './util/Sort';

class List extends Component {
  state = {
    sorters: this.props.sorters
  };

  static defaultProps = {
    sorters: [{
      property: 'name'
    }, {
      property: 'company'
    }]
  };

  componentDidMount() {
    fetch('/data.json')
      .then(res => res.json())
      .then(this.onLoad);
  }

  parseData(data) {
    const { sorters } = this.state;

    if (data && data.length) {
      if (Array.isArray(sorters) && sorters.length) {
        data.sort(createSorter(...sorters));
      }
    }

    return data;
  }

  onLoad = data => {
    this.setState({
      data: this.parseData(data)
    });
  };

  render() {
    const { data } = this.state;

    return data ? this.renderData(data) : this.renderLoading();
  }

  renderData(data) {
    if (data && data.length > 0) {
      return (
        <div>
          {data.map(item => (
            <div key={item.id}>
              <a href={`mailto:${item.email}`}>{item.name}</a> {item.company}
            </div>
          ))}
        </div>
      );
    } else {
      return <div>No items found</div>;
    }
  }

  renderLoading() {
    return <div>Loading...</div>;
  }
}

export default List;

Sorting Example

import React from 'react';
import List from './List'

export default () => <List />;


Ext JS to React: Load, Sort and Filter Data with React



Now when the data is loaded, the data will automatically be sorted using the sorters property within the parseData. Now you can see why we split this out into its own method. Here we set a default sorters array in the static defaultProps but the sorters property could be set when instantiating the List class:

const sorters = [{ property: 'name', direction: 'DESC' }]
<List sorters={sorters} />

To do remote sorting, you would have to append a URL parameter to the URL passed to the fetch function and do not add the sorting code to parseData:

  componentDidMount () {
    const { sorters } = this.state

    fetch(`/data.json?sort=${JSON.stringify(sorter)}`)
      .then(res => res.json())
      .then(this.onLoad.bind(this))
  }

You can already start to envision how this could be set as a prop on the List class or even on the List’s state to sort either locally or remotely. The difference is whether you pass the sort information as a URL parameter or if you use a sort function locally.

You can review the sample code on the Git repo.

Data Filtering Utility Functions

The other common functionality of an Ext JS data store is to filter data. Like we did with the Sort module, we can create a Filter.js within the src/util/ directory that can handle the filtering in an abstract way:

const doFilter = (item, filter) => {
  let { value } = filter;

  if (!(value instanceof RegExp)) {
    value = filter.value = new RegExp(value, 'i');
  }

  return value.test(item[ filter.property ]);
}

const createFilter = (...filters) => {
  if (typeof filters[0] === 'string') {
    filters = [
      {
        property: filters[0],
        value: filters[1]
      }
    ];
  }

  return item => filters.every(filter => doFilter(item, filter));
};

export { createFilter };

Filtered List Class

To use the createFilter function, we want to handle filtering in the renderData method of our List class. The reason to do this within the renderData (render phase) is we want to keep the data we got remotely as it is. This is so if we want to be able to change filters only in the client, we have the entire data array and we can apply filters when rendering. This way, setting the state will automatically pick up any filter change without you having to do anything in the List. The List class should now look like:

import React, { Component } from 'react';
import { createFilter } from './util/Filter';
import { createSorter } from './util/Sort';

class List extends Component {
  state = {
    filters: this.props.filters,
    sorters: this.props.sorters
  }

  static defaultProps = {
    filters: [{
      property: 'name',
      value: 'dori'
    }, {
      property: 'company',
      value: 'a'
    }],

    sorters: [{
      property: 'name'
    }, {
      property: 'company'
    }]
  }

  componentDidMount () {
    fetch('/data.json')
      .then(res => res.json())
      .then(this.onLoad);
  }

  parseData (data) {
    const { sorters } = this.state;

    if (data && data.length) {
      if (Array.isArray(sorters) && sorters.length) {
        data.sort(createSorter(...sorters));
      }
    }

    return data;
  }

  onLoad = (data) => {
    this.setState({
      data: this.parseData(data)
    });
  }

  render () {
    const { data } = this.state;

    return data ?
      this.renderData(data) :
      this.renderLoading();
  }

  renderData (data) {
    if (data && data.length > 0) {
      const { filters } = this.state;

      if (Array.isArray(filters) && filters.length) {
        data = data.filter(createFilter(...filters));
      }

      return (
        <div>
          {
            data.map(item => (
              <div key={item.id}>
                <a href={`mailto:${item.email}`}>{item.name}</a> {item.company}
              </div>
            ))
          }
        </div>
      );
    } else {
      return <div>No items found</div>;
    }
  }

  renderLoading () {
    return <div>Loading...</div>;
  }
}

export default List;

Filtering Example

import React from 'react';
import List from './List'

export default () => <List />;


Ext JS to React: Load, Sort and Filter Data with React



The list will now use the default filters when rendering the data. Sorting will happen when the data is loaded and the filtering will happen when the data is being rendered.

Like sorting, you can also provide filters as a prop:

const filters = [{ property: 'name', value: 'rico' }]
<List filters={filters} />

You can review the sample code on the Git repo.

Conclusion

Loading remote data and sorting and filtering that data is simple using native JavaScript. Ext JS initially supplied the functionality prior to native array sorting and filtering. However, browsers have now been supporting these functions since IE9. Ext JS does have several built-in proxies and readers to handle different transports and formats so if you need to deal with something more than JSON, you’ll have to provide your own or search npm to see if a pre-built option exists.


Like What You See?

Got any questions?