Using Azure Mobile Apps from a React Redux App

I did some work towards my React application in my last article – specifically handling authentication with Auth0 providing the UI and then swapping the token with Azure Mobile Apps for a ZUMO token. I’m now all set to do some CRUD operations within my React Redux application. There is some basic Redux stuff in here, so if you want a refresher, check out my prior Redux articles:

Refreshing Data

My first stop is “how do I get the entire table that I can see from Azure Mobile Apps?” This requires multiple actions in a React Redux world. Let’s first of all look at the action creators:

import constants from '../constants/tasks';
import zumo from '../../zumo';

/**
 * Internal Redux Action Creator: update the isLoading flag
 * @param {boolean} loading the isLoading flag
 * @returns {Object} redux-action
 */
function updateLoading(loading) {
    return {
        type: constants.UpdateLoadingFlag,
        isLoading: loading
    };
}

/**
 * Internal Redux Action Creator: replace all the tasks
 * @param {Array} tasks the new list of tasks
 * @returns {Object} redux-action
 */
function replaceTasks(tasks) {
    return {
        type: constants.ReplaceTasks,
        tasks: tasks
    };
}

/**
 * Redux Action Creator: Set the error message
 * @param {string} errorMessage the error message
 * @returns {Object} redux-action
 */
export function setErrorMessage(errorMessage) {
    return {
        type: constants.SetErrorMessage,
        errorMessage: errorMessage
    };
}

/**
 * Redux Action Creator: Refresh the task list
 * @returns {Object} redux-action
 */
export function refreshTasks() {
    return (dispatch) => {
        dispatch(updateLoading(true));

        const success = (results) => {
            console.info('results = ', results);
            dispatch(replaceTasks(results));
        };

        const failure = (error) => {
            dispatch(setErrorMessage(error.message));
        };

        zumo.table.read().then(success, failure);
    };
}

Four actions for a single operation? I’ve found this is common for Redux applications that deal with backend services – you need to have several actions to implement all the code-paths. I could have gotten away with just three – an initiator, a successful completion and an error condition. However, I wanted to ensure I had flexibility. The setErrorMessage() and updateLoading() actions are generic enough to be re-used for other actions.

Two of these actions are internal – I don’t export them and so the rest of the application never sees them. The only action creator that the application at large can use is the refreshTasks() action – the initiator for the refresh. I’ve made the setErrorMessage() task generic enough that it can be used by an error dialog to clear the error as well. Lesson learned – only export the tasks that you want the rest of the application to use.

Looking at the refreshTasks() task list, I’m not doing any filtering. Azure Mobile Apps supports filtering on the server as well as the client. I’d rather filter on the client in this application – it saves a round trip and the data is never going to be big enough that filtering is going to be a problem. This may not be true in your application – you should make a decision on filtering on the server. vs. client in terms of performance and memory usage.

Insert, Modify and Delete Tasks

I’ve already got the actions – I just need to update them for the async server code. For example, here is the insert code:

/**
 * Redux Action Creator: Insert a new task into the cache
 * @param {Object} task the task to be updated
 * @param {string} task.id the ID of the task
 * @param {string} task.text the description of the task
 * @param {bool} task.complete true if the task is completed
 * @returns {Object} redux-action
 */
function insertTask(task) {
    return {
        type: constants.Create,
        task: task
    };
}

/**
 * Redux Action Creator: Create a new Task
 * @param {string} text the description of the new task
 * @returns {Object} redux-action
 */
export function createTask(text) {
    return (dispatch) => {
        dispatch(updateLoading(true));

        const newTask = {
            text: text,
            complete: false
        };

        const success = (insertedItem) => {
            console.info('createTask: ', insertedItem);
            dispatch(insertTask(insertedItem));
        };

        const failure = (error) => {
            dispatch(setErrorMessage(error.message));
        };

        zumo.table.insert(newTask).then(success, failure);
    };
}

I’m reusing the updateLoading() and setErrorMessage() action creators that I used with the refresh tasks. The createTask() does the insert async then calls the insertTask() action creator with the newly created task to update the in-memory cache (as we will see below when we come to the reducers). There are similar mechanisms for modification and deletion. I create an internal action creator to update the in-memory cache. The exported action creator initiates the change and doesn’t update the in-memory cache until the request is completed successfully.

I did need to do some work to add a dialog on the error message being set in my Application.jsx component:

        const onClearError = () => { return dispatch(taskActions.setErrorMessage(null)); };
        let errorDialog = <div style={{ display: 'none' }}/>;
        if (this.props.errorMessage) {
            const actions = [ <FlatButton key="cancel-dialog" label="OK" primary={true} onTouchTap={onClearError} /> ];
            errorDialog = (
                <Dialog
                    title="Error from Server"
                    actions={actions}
                    modal={true}
                    open={true}
                    onRequestClose={onClearError}
                >
                    {this.props.errorMessage}
                </Dialog>);
        }

I then place {errorDialog} in my rendered JSX file.

Adjusting the Cache

Let’s take a look at the reducers.

import constants from '../constants/tasks';

const initialState = {
    tasks: [],
    profile: null,
    isLoading: false,
    authToken: null,
    errorMessage: null
};

/**
 * Reducer for the tasks section of the redux implementation
 *
 * @param {Object} state the current state of the tasks area
 * @param {Object} action the Redux action (created by an action creator)
 * @returns {Object} the new state
 */
export default function reducer(state = initialState, action) {
    switch (action.type) {
    case constants.StoreProfile:
        return Object.assign({}, state, {
            authToken: action.token,
            profile: action.profile
        });

    case constants.UpdateLoadingFlag:
        return Object.assign({}, state, {
            isLoading: action.isLoading
        });

    case constants.Create:
        return Object.assign({}, state, {
            isLoading: false,
            tasks: [ ...state.tasks, action.task ]
        });

    case constants.ReplaceTasks:
        return Object.assign({}, state, {
            isLoading: false,
            tasks: [ ...action.tasks ]
        });

    case constants.Update:
        return Object.assign({}, state, {
            isLoading: false,
            tasks: state.tasks.map((tmp) => { return tmp.id === action.task.id ? Object.assign({}, tmp, action.task) : tmp; })
        });

    case constants.Delete:
        return Object.assign({}, state, {
            isLoading: false,
            tasks: state.tasks.filter((tmp) => { return tmp.id !== action.taskId; })
        });

    case constants.SetErrorMessage:
        return Object.assign({}, state, {
            isLoading: false,
            errorMessage: action.errorMessage
        });

    default:
        return state;
    }
}

You will note that my reducers only deal with the local cache. I could, I guess, also store this in localStorage so that my restart speed is faster. There would be a more complex interaction between the server, the in-memory cache and the localStorage cache that would have to be sorted out, however.

Note that all my reducers that result in a change to the in-memory cache also turn off the isLoading flag. This allows me one less dispatch via redux. I doubt it’s a significant performance increase, but I’m of the opinion that any obvious performance wins should be done. In this case, each operation results in one less action dispatch and one less Object.assign. In bigger projects, this could be significant.

Thinking about Sync and Servers

One of the things you can clearly see in this application is the delay in the round-trip to the server. I don’t update the cache until I have updated the server. This is safe. However, it’s hardly performant. There are a couple of ways I could fix this.

Firstly, I can update the local cache first. For example, let’s say I am inserting a new object. I can add two new local fields that are not transmitted to the server: clientId and isDirty. When the task is newly created, I can create a clientId instead (and use that everywhere) and set the dirty flag. When the server response comes back, I update the record from the server (not updating clientId) and clear the dirty flag. This allows me to identify “things that have not been updated on the server”, perhaps preventing multiple updates – it also allows me to identify things that have been newly created on the client.

Secondly, I can update a localStorage area instead of the server. This will be much faster. Then, periodically, I can trigger a refresh of the data from the server – sending the changes to the localStorage area up to the server.

There are multiple ways to do synchronization of data with a server – which one depends on the requirements of accuracy of the data on the server, performance required on the client and memory consumption. There are trade offs whichever way you choose.

Where’s the code

You can find the complete code on my GitHub Repository.