Reusable React Code Modules, Part 8 - Offline State
Ensuring Reliability: Creating Reusable Modules for Offline State Management
Ensuring that web applications remain functional even without an internet connection is essential for a seamless user experience. Managing offline state in React involves handling data persistence, synchronization, and providing offline capabilities. This guide covers how to structure reusable offline state modules in React, handle offline-first functionality, and manage data synchronization.
Common Libraries and Tools
1. Workbox
Workbox is a set of libraries and Node modules that simplify the process of adding offline support to web apps
It provides a way to run the Workbox modules
importScripts('/third_party/workbox-vX.Y.Z/workbox-sw.js');
workbox.setConfig({
modulePathPrefix: '/third_party/workbox-vX.Y.Z/',
});
https://developer.chrome.com/docs/workbox/modules/workbox-sw
These files will be cached by your browser making them available for future offline use
Key Features
Pre-caching and runtime caching
Strategies for caching
Background sync
Service worker generation
2. LocalForage
LocalForage is a library for offline storage, improving the storage capacity and performance of web applications by using IndexedDB, WebSQL, or localStorage
https://github.com/localForage/localForage
https://localforage.github.io/localForage
Key Features
Asynchronous storage (IndexedDB, WebSQL, localStorage)
Simple API
Promises-based
3. Redux Persist
Redux Persist is a library that enables persistence and rehydration of Redux state
https://github.com/rt2zz/redux-persist
https://www.npmjs.com/package/redux-persist
https://redux-toolkit.js.org/rtk-query/usage/persistence-and-rehydration
Key Features
Persist and rehydrate Redux state
Storage adapters (localStorage, sessionStorage, custom)
Integration with other Redux middleware
4. PouchDB
PouchDB is an open-source JavaScript database that syncs with CouchDB, providing a NoSQL database in the browser
https://github.com/pouchdb/pouchdb
https://www.npmjs.com/package/pouchdb
Key Features
Client-side NoSQL database
Synchronization with CouchDB
Offline-first design
Query and indexing capabilities
Comparison
Workbox: Best for comprehensive offline capabilities, including caching strategies and background sync
LocalForage: Ideal for improving storage capacity and performance with a simple API
Redux Persist: Suitable for applications using Redux for state management, providing easy state persistence and rehydration
PouchDB: Best for applications requiring a client-side NoSQL database with offline-first capabilities and synchronization
Examples
Example 1: Workbox
Setup:
// service-worker.js
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
precacheAndRoute(self.__WB_MANIFEST);
registerRoute(
({ request }) => request.destination === 'image',
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
{
cacheWillUpdate: async ({ request, response }) => {
return response && response.status === 200 ? response : null;
},
},
],
})
);
Integration with Create React App:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorkerRegistration.register();
Example 2: LocalForage
Setup:
import localforage from 'localforage';
localforage.config({
driver: localforage.INDEXEDDB,
name: 'myApp',
version: 1.0,
storeName: 'keyvaluepairs',
description: 'some description',
});
Usage:
import React, { useEffect, useState } from 'react';
import localforage from 'localforage';
const DataComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
localforage.getItem('myData').then((value) => {
if (value) {
setData(value);
} else {
// Fetch from API and store
fetch('/api/data')
.then((response) => response.json())
.then((data) => {
setData(data);
localforage.setItem('myData', data);
});
}
});
}, []);
return (
<div>
<h1>Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
};
export default DataComponent;
Example 3: Redux Persist
Setup:
// store.js
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import rootReducer from './reducers';
const persistConfig = {
key: 'root',
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(persistedReducer);
export const persistor = persistStore(store);
Integration with React:
// App.js
import React from 'react';
import { PersistGate } from 'redux-persist/integration/react';
import { Provider } from 'react-redux';
import { store, persistor } from './store';
import MyComponent from './MyComponent';
const App = () => (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<MyComponent />
</PersistGate>
</Provider>
);
export default App;
Example 4: PouchDB
Setup:
import PouchDB from 'pouchdb';
const db = new PouchDB('my_database');
db.info().then(function (info) {
console.log(info);
});
Usage:
import React, { useEffect, useState } from 'react';
import PouchDB from 'pouchdb';
const db = new PouchDB('my_database');
const DataComponent = () => {
const [data, setData] = useState([]);
useEffect(() => {
db.allDocs({ include_docs: true, descending: true })
.then((doc) => {
setData(doc.rows.map((row) => row.doc));
})
.catch((err) => console.error(err));
}, []);
return (
<div>
<h1>Data</h1>
<ul>
{data.map((item) => (
<li key={item._id}>{item.title}</li>
))}
</ul>
</div>
);
};
export default DataComponent;