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: Handling Data with Redux

Published on April 17, 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.

In a previous article, Handling Application State with Redux, we saw how to use static data to load values into a form and keep the application state in sync as a user edits the form. However, in the real world data may not be already loaded or generated within the application. More than likely, we would need to load the data remotely. In this article, we’ll start with the form we built in the previous Redux article and show how to add remote loading into the mix.

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.

Sync to Async Data Fetching

When we first looked at Redux, we saw that dispatching actions is a synchronous flow. However, loading remote data is performed asynchronously. Obviously this breaks the flow of our previous Redux setup. To handle asynchronous actions, we need to use a Redux middleware solution like redux-thunk. redux-thunk is a popular library for handling side effects in a React app. It handles asynchronous code within an app using Redux. Other popular libraries designed to assist in localizing the handling of side effects are redux-saga, redux-observable, and redux-promise.

redux-thunk allows a function to be returned in a store.dispatch() call. Within this returned function, there is a callback function argument that will finish the dispatching of the result in an asynchronous flow. Let’s take a look at how we can modify the form from the previous article.

First, install redux, react-redux, redux-thunk, and optionally redux-devtools (for those developing with Chrome);

npm install --save redux
npm install --save react-redux
npm install --save redux-thunk
npm install --save redux-devtools

redux-thunk Middleware Setup

We can edit src/index.js to add the middleware to Redux:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';                         // add this line
import thunk from 'redux-thunk';                                // add this line
import { applyMiddleware, createStore } from 'redux';           // add this line
import { composeWithDevTools } from 'redux-devtools-extension'; // add this line
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import userApp from './reducers';                               // add this line

const store = createStore(userApp, composeWithDevTools(applyMiddleware(thunk))); // add this line 

ReactDOM.render(
  <Provider store={store}>                                       // add this line
    <App />
  </Provider>,                                                   // add this line
  document.getElementById('root')
);

registerServiceWorker();

That’s all we need to setup redux-thunk with Redux as it’s a simple middleware. Not a bad setup instruction!

User Reducer Defined

Next, we need to edit our user reducer (src/reducers/user.js) to modify the default user and add in the handler for when the user data is loaded:

import { LOAD_USER, UPDATE_USER } from '../actions';

const DEFAULT_USER = {
  id: 0,
  name: '',
  email: '',
  phone: '',
  company: '',
  department: '',
  title: ''
};

function user(userData = DEFAULT_USER, action) {
  switch (action.type) {
    case UPDATE_USER:
      return Object.assign({}, userData, action.payload);
    case LOAD_USER:
      return Object.assign({}, action.payload);
    default:
      return userData;
  }
}

export default user;

You may be wondering why the default user is a bunch of strings. For this simple example, the data passed to the form component needs to have defined values. Undefined values results in React throwing an error saying that we are switching from uncontrolled to controlled components. Having empty strings will circumvent that error. We also have a case clause to listen for the user load action where it’ll return the user that was passed with the action.

Combining Reducers with the Reducer Index

The user reducer is combined in src/reducers/index.js. The reducer index file (and ultimately all combined reducers) is referenced as userApp in the src/index.js file:

import { combineReducers } from 'redux';
import user from './user';

const userApp = combineReducers({ user });

export default userApp;

User Action Defined

To do the actual loading, we need to define a new user action creator (src/actions/user.js):

export const LOADING_USER = 'LOADING_USER';
export const LOAD_USER = 'LOAD_USER';
export const LOAD_USER_ERROR = 'LOAD_USER_ERROR';
export const UPDATE_USER = 'UPDATE_USER';

export function loadUser(id) {
  return dispatch => {
    dispatch({
      type: LOADING_USER,
      payload: {
        id
      }
    });

    return fetch('/user.json')
      .then(res => res.json())
      .then(
        payload =>
          dispatch({
            type: LOAD_USER,
            payload
          }),
        payload => // error
          dispatch({
            type: LOAD_USER_ERROR,
            payload
          })
      );
  };
}

export function updateUser(payload) {
  return {
    payload,
    type: UPDATE_USER
  };
}

We create four constants to hold two new action types: one for when a load has started, one for when the loading has finished, one for if the user load has failed, and another if the user is updated. This loadUser action creator returns a function that takes the dispatch callback function. Within this function, we dispatch the user loading action so that we can call any interim functions such as masking a view (see the Floating Components article for more about masking a component). We do this to let the user know that something is happening while we fire off the actual loading using the Fetch API. If the loading was successful, we dispatch the load user action (LOAD_USER). Else, the load user error (LOAD_USER_ERROR) action will be dispatched.

Combining Actions with the Action Index

The user action is made available to any containers using react-redux in src/actions/index.js:

import { LOAD_USER, loadUser, UPDATE_USER, updateUser } from './user';

export { LOAD_USER, loadUser, UPDATE_USER, updateUser };

react-redux Form Container

The loadUser and updateUser functions are then imported with import { loadUser, updateUser } from '../../actions'; in the example below. We also need to add a prop that gains access to the dispatch function so that we can load user data. To do this, we need to edit src/containers/user/Update.js to add the function to mapDispatchToProps:

import { connect } from 'react-redux';
import { loadUser, updateUser } from '../../actions';
import Form from '../../components/user/Form';

const mapStateToProps = state => Object.assign({}, state.user);

const mapDispatchToProps = dispatch => ({
  loadUser: id => dispatch(loadUser(id)),

  onFieldChange: e => {
    const { name, value } = e.target;
    const change = { [name]: value };

    dispatch(updateUser(change));
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(Form);

React Form Class

These modifications turn our synchronous Redux form example into an asynchronous, remote loading example. The only thing we need to do is to trigger the loadUser function. This should be triggered within the Form component just before the form’s structure is returned (props.loadUser(props.id);):

import React from 'react';

const renderField = (props, name, label = name, type = 'text') => (
  <div style={{ marginBottom: '12px' }}>
    <label style={{textTransform: 'capitalize'}}>
      {label}
      <input
        type="text"
        name={name}
        value={props[name]}
        onChange={props.onFieldChange}
        style={{ marginLeft: '12px' }}
      />
    </label>
  </div>
);

const Form = props => {
  props.loadUser(props.id);

  const submit = e => {
    e.preventDefault();

    // do submit
  };

  return (
    <form>
      {renderField(props, 'name')}
      {renderField(props, 'email', undefined, 'email')}
      {renderField(props, 'phone', 'Phone Number', 'tel')}
      {renderField(props, 'company')}
      {renderField(props, 'department')}
      {renderField(props, 'title')}

      <button onClick={submit}>Submit</button>
    </form>
  );
};

export default Form;

Sample User Data

The form data you’ll see in the browser now comes from public/user.json since it’s now being remotely loaded.

{
  "id": 1,
  "name": "Don Draper",
  "email": "don.draper@scdp.com",
  "phone": "+1 (212) 555-0112",
  "company": "Sterling Cooper Draper Pryce",
  "department": "Marketing",
  "title": "Creative Director"
}

Asynchronous Form Example

To create an instance of the form, we’ll use the following src/App.js:

import React from 'react'
import Form from './containers/user/Update'

export default () => <Form />;
Ext JS to React: Handling Data with Redux

Conclusion

The way Redux operates using abstracted out reducers and action creators is not the same as what we see used in Ext JS. Once you have them created, the API to subsequently load the data is similar to a form.load() you would do with Ext JS. Of course, handling asynchronous calls is not limited to loading data for a form. You can load anything with the methods shown such as fetching an array for a list, grid, or chart.

As you develop an application, you’ll likely identify opportunities to abstract aspects of your code as you’ll likely introduce duplicate code when loading remote data for multiple components within the app. The next blog article in the series will continue our look at handling data queries, but within the context of another popular state manager, MobX.

Posted in Application Development
Share this

Mitchell Simoens

Mitchell Simoens is a Senior Front End Engineer at Modus Create. Mitchell has spent the last 10 years working with Ext JS including developing core functionality, Sencha Fiddle and (I hope your insurance covers blown minds) supporting the online community with over 40,000 posts on the Sencha Forums. Before working with Ext JS, Mitchell used Perl and PHP but loves spending time with Node JS for today's needs. When not working, you can find Mitchell relaxing with his wife and daughter, or developing his talents as an amateur furniture maker.
Follow

Related Posts

  • 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…

  • 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…

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