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.
NEW RESEARCH: LEARN HOW DECISION-MAKERS ARE PRIORITIZING DIGITAL INITIATIVES IN 2024.
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 />;
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 />;
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 />;
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.
Seth Lemmons
Related Posts
-
Ext JS to React: FAQ
This is part of the Ext JS to React blog series. React is Facebook's breakout…
-
Ext JS to React: Migration to Open Source
Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…