Adding Offline Support to Redux

Ian Ovenden
7 min readAug 9, 2017

--

This article details the implementation of offline support into a React/Redux application architecture. Providing network-resilience is vital for users within developing nations that lack a robust mobile-data infrastructure, but can also improve the performance and stability of your app in general.

The code changes here build on the WebSocket development steps outlined in my previous article.

Why?

In the UK we often take for granted a persistent, fast, reliable data connection but this isn’t always the case. For example, you might be on a train moving between areas of variable coverage or perhaps attending an event where mobile network load is limiting connectivity. In other areas of the world you might be considered lucky if a patchy network is the norm.

In 2016 I was lucky enough to catch an inspiring talk from Bruce Lawson which covered the state of Web accessibility in developing countries and the drive to provide a Web for all (the clue really is in the name — World Wide Web!!). For further reading, I’d certainly recommend Bruce’s article below which dives into detail on the challenges faced within these network poor areas and offers some potential solutions to unlocking a large (and largely untapped) commercial market.

Wouldn’t it be great if our app was responsive to such instability? So user productivity isn’t impacted by such, often temporary, network interruptions.

As developers we often talk about our apps being responsive — we’re usually referring to how content is displayed on multiple viewports/devices, but it is equally important that our app responds to deviations in network performance and context of use.

Of course, importance placed on network resilience is dictated by the type of app and target user base. An app which works to sync vital healthcare data within an African state will place far more importance on a network resilient architecture than a ToDo app which allows a handful of users to sync up their daily tasks.

Such considerations however, benefit all users.

What?

Please note, this article focuses on Redux and it’s offline state management capabilities — there are a number of other conditions your app must meet before it is available offline. Details of how to build Progressive Web Applications (PWAs) and the implementation of offline functionality exposed by Service Workers are out of scope here but have been covered in numerous articles and tutorials.

An introduction to Server Workers by Matt Gaunt would be a good place to start:

Once your app is available offline or during spells of degraded network performance, it would be desirable if users could still perform interactions and complete goals as if still online — not just view a cached page?

Fortunately, with it’s transactional approach to state management, redux is well suited to timeline navigation — rollbacks for example. This also makes it a viable candidate for handling our offline capabilities.

Just when I was considering how to implement offline functionality, Redux Offline and this excellent article by it’s creator Jani Eväkallio came to light. It really is an excellent read and clearly explains the functional gaps and technical considerations that fed into the design of the library.

This article provided a high level overview of the solution I was looking for so I dived into the source code to determine how I might integrate it into my own web application.

How?

Redux Offline is incredibly easy to install and as the readme suggests the library can be integrated into your app within 30 mins!!

In summary, Redux Offline adds it’s own branch to the state tree for queueing up and dispatching actions that have been wrapped in offline meta data. In order for this and the rest of the state to be available offline, it has to be persisted to disk. This is done via IndexedDB by default but you can configure the disk storage type. The state can then be automatically loaded on startup, regardless of connection state.

The first step, install the library yarn add redux-offline.

This library, along with its default configuration can then be imported into the store configuration file and added to the store through compose. For this integration I’ve stuck with the default configuration, but I’d encourage you to investigate the options available — this is certainly something I’m going to follow up on.

import { createStore, applyMiddleware, compose } from 'redux';
import { offline } from 'redux-offline';
import offlineConfig from 'redux-offline/lib/defaults';

import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import reducer from '../reducers';
import { init as websocketInit, emit } from '../actions/websocket';
const loggerMiddleware = createLogger();
const middleware = [ thunkMiddleware.withExtraArgument({ emit }) ];
export default function configureStore( preloadedState ) { const store = createStore(
reducer,
preloadedState,
compose(
applyMiddleware(
...middleware,
loggerMiddleware
),
offline( offlineConfig )
)
);
websocketInit( store );
return store;
}

Finally, we wrap our actions in offline meta data where required. Continuing the example from the previous article, below is how the stage title update and broadcast function of the stage action file now looks. This function handles the update of a given stage title and broadcasts a WebSocket message to other connected devices.

export function wsStageTitle( stageId, stageTitle ) {
return ( dispatch, getState, {emit}) => {
dispatch({
type: UPDATE_STAGE_TITLE,
payload: {
stageId: stageId,
stageTitle: stageTitle
},
meta: {
offline: {
effect: { url: '', method: 'GET' },
commit: {
type: 'UPDATE_STAGE_TITLE',
meta: {
stageId: stageId,
stageTitle: stageTitle
}
}
rollback: {
type: 'UPDATE_STAGE_TITLE_ROLLBACK',
meta: {
stageId: stageId
}
}
}
}

}),
emit(
messageTypes.UPDATE_STAGE_TITLE, {
stageId: stageId,
stageTitle: stageTitle
}
);
};
}

Redux Offline will parse any actions wrapped in offline meta-data and perform the appropriate course of action based on whether or not network connectivity has been detected.

The effect value is essentially the test the library will perform to determine whether or not the app is online. In the above example, the URL is left blank — this will perform a test on the domain name (for general availability) but we could check the path to a specific API endpoint.

If the effect returns true, the connection is determined to be up and the action specified under the commit is dispatched — “UPDATE_STAGE_TITLE” in this case, along with the stageId and stageTitle forming the payload.

If the test returns false, the offline module will retry it at given intervals as per the configuration (e.g. 1 second, 3 seconds, 5 seconds, 10 seconds, 30 seconds, 1 minute etc). If it encounters an error (HTTP status code 4xx) or the final scheduled test returns a HTTP status code of 5xx then the message will be discarded and the action specified against the rollback will execute. In this example “UPDATE_STAGE_TITLE_ROLLBACK”.

Below is an illustration of how this new functionality fits within the Redux architecture of the WebSockets app discussed previously.

Fig 1. Redux WebSockets and Offline Capable Architecture.

In action

Using the Redux DevTools plugin, lets take a look at the module in action. Our initial state when a board loads:

Fig 2. Initial board load

Along with our first class entities (boards, stages and tickets), we can see that Redux Offline has added an “offline” branch for handling actions.

When a user updates the title of one of the stages, this dispatches the “UPDATE_STAGE_TITLE” action. As this action is wrapped in offline meta-data it is interpreted by the offline module. The action is added to the outbox for dispatch when online.

Fig 3. UPDATE_STAGE_TITLE added to outbox to be dispatched.

If the app is online the action is dispatched from the outbox almost instantaneously. Otherwise it will be queued up and sent sequentially when the app regains connectivity.

For a WebSocket supported action type, the Emit() will fire as normal as part of the action dispatch — so messages will be broadcast when back online to sync everyone with the latest changes.

What about handling WebSocket messages not received when offline?

That’s a great question. The offline functionality described here covers how a user can continue to perform tasks offline and when back online broadcast messages to other users over WebSockets. But how do we get the latest changes that may have been made by other users when we were offline. This is something that needs consideration.

Conclusion

The transactional nature of Redux is well suited to handling actions when network connectivity is compromised. The Redux Offline library brings together a number of technical capabilities allowing for ease of integration, configuration and scalability.

This is an elegant solution for providing network resilience to your application that allows users to remain productive. It integrates well into existing Redux architectures and is very easy to setup.

Following on from this I have written an article on how to construct a test suite for a React, Redux and WebSocket application — please take a look:

--

--

Ian Ovenden

Front-end specialist passionate about a web for everyone 🌏. Powered by Rock 🎸 Mexican food 💀 and coffee ☕.