Sencha Ext JS 7.7 is Here – Discover What’s New and Exciting – LEARN MORE

Using ExtReact Stores in Flux Apps

May 25, 2017 112 Views
Show

ExtReact provides ready-to-use UI components for building data-intensive web apps using React. You get all the components you need in a single package, including grids, trees, charts, layouts, animations, advanced visualizations, forms, dialogs, and more. Check out the ExtReact Kitchen Sink to see all of the components in action. Give it a try by signing up for a free 30-day trial. In this article, I’ll show you how to use Ext.data.Store in Flux apps.

A Store by Any Other Name

Both ExtReact and Flux use the term “store”. ExtReact’s Ext.data.Store class provides high performance data retrieval, paging, sorting, and filtering for components that display large sets of records such as Grid, Tree, and List. Ext.data.Store should not be confused with the concept of a store in Flux. A Flux store is an observable data structure that holds the state of your application. To illustrate the relationship between these two data structures and how to best connect them, let’s use the popular Redux library as an example.

Consider the following UI from the ExtReact REST Example, which contains a Grid and a set of search criteria.

ExtReact REST Example

When search criteria change, we apply a filter to the grid’s store using the filter method. The store either filters the data in memory, or if a remote proxy is configured, forwards the filter criteria to the server in a fetch request.

There are two possibilities for where an Ext.data.Store should reside in an app that uses Redux.

Option 1: In the Redux Store

The Ext.data.Store instance bound to our Grid can reside in the application state stored by Redux. When search criteria change, our reducer can apply a new filter to the store:

reducer.js

import { UPDATE_CRITERIA } from './actions';

const initialState = {
    employeesStore: new Ext.data.Store({
        ...
    })
}

export default function reducer(state = initialState, action) {
    switch(action.type) {
        case UPDATE_CRITERIA:
            const { criteria } = action;
            const filters = []; 

            for (let name in criteria) {
                filters.push({
                    property: name,
                    value: criteria[name]                   
                })
            }

            state.employeesStore.filter(filters);
            
            return { ...state };
    }
}

In this scenario, our Grid component can be implemented as a pure function. We connect it to the redux store using the connect function from react-redux:

EmployeesGrid.js

import React from 'react';
import { Grid } from '@extjs/ext-react';
import { connect } from 'react-redux';

export default function EmployeesGrid({ store }) {
    return (
        
    )
}

const mapStateToProps = (state) => {
    return { store: state.employeesStore }
};

export default connect(mapStateToProps)(EmployeesGrid);

Redux state is a convenient place for the Ext.data.Store instance for two reasons:

  1. You can easily make the store available to multiple components. For example, you might want to use the same store on both a grid and a chart to provide two different views of the same data.
  2. The EmployeesGrid component can be implemented as a simple function because all of the logic related to filtering the store resides in our reducer.

This design, however, doesn’t follow the best practice of making Redux state immutable. Some developers may not be comfortable with this approach. There are interesting discussions on GitHub about storing mutable objects within Redux state. Read Integrating mutable dependencies with redux #606 and Have any suggestions for where to put something like an Esri or Leaflet Map object in Flux architecture #1279. As you might expect, opinions are split on the topic. Some developers find this approach to be expedient and without meaningful side effects, while others prefer their Redux state to remain strictly immutable.

Option 2: In Component State

If we wish to preserve the immutability of our Redux state, we can instead store only the individual criteria in application state, and let our EmployeesGrid component manage the Ext.data.Store using lifecycle methods. With this design our reducer looks like this:

reducer.js

import { UPDATE_CRITERIA } from './actions';

const initialState = {
    criteria: { }
}

export default function reducer(state = initialState, action) {
    switch(action.type) {
        case UPDATE_CRITERIA:
            const { criteria } = action;
            return { ...state, criteria };
    }
}

We shift the responsibility for managing our Ext.data.Store to the EmployeesGrid component:

EmployeesGrid.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Grid } from '@extjs/ext-react';
import { connect } from 'react-redux';
 
export default class EmployeesGrid extends Component {
 
    static propTypes = {
        criteria: PropTypes.object
    };
 
    store = new Ext.data.Store({
        ...
    });
 
    componentDidUpdate(prevProps) {
        const { criteria } = this.props;
 
        if (prevProps.criteria !== criteria) {
            const filters = [];
 
            for (let name in criteria) {
                filters.push({
                    property: name,
                    value: criteria[name]                   
                })
            }
 
            this.store.filter(filters)
        }
    }
 
    render() {
        return (
            
        )
    }
}
 
const mapStateToProps = (state) => {
    return { criteria: state.criteria }
};
 
export default connect(mapStateToProps)(EmployeesGrid);

This approach has several benefits:

  • Our redux state objects remain immutable, in keeping with the best practice.
  • Our reducer is a pure function, and it deals only with plain JS objects.
  • The implementation details of our integration with Ext.data.Store are nicely hidden within our component. In this way, toolkit-specific API calls are confined to the component that needs them (EmployeesGrid).

Conclusion

In my opinion, both approaches have their merits, but option #2 (putting Ext.data.Store in component state) is more in keeping with the spirit of Redux. In scenarios where stores need to be accessed by multiple components, such as a grid and a chart, I recommend creating a parent component that contains both and manages the Ext.data.Store using lifecycle methods. For a full example that combines ExtReact, Redux, and a back-end API implemented with Node and Express, check out the ExtReact REST example.

Let us know which approach works best for you. Share your experience in the comments section below.

coming soon

Something Awesome Is

COMING SOON!