How to make an offline first React App
Muhammad Anser
Software Craftsman ?? | Teacher ???? | Speaker ?? | Writer ??? | Hands-on Developer ??????
I did a POC on offline first React app and decided to share my POC with the developer community.
Suppose we are to develop a ReactJS application in which we can’t afford any pause in CRUD operations even if there is any network issue, we will see how we can achieve this. I will be using Redux for store management.
Let’s start by installing necessary libraries:
npm install redux-offline --save
This library is very handy in building offline-first apps both for web and react-native.
Then install “localforage” as I will be using indexedDB instead of localStorage.
npm install localforage --save
Other libraries which we will be using in this example are:
- redux - redux-thunk - redux-logger - react-redux
You can read in detail about the above mentioned libraries by clicking their names.
Redux store will look like the following:
import { applyMiddleware, createStore, compose } from "redux"; import { offline } from "redux-offline"; import offlineConfig from "redux-offline/lib/defaults"; import thunk from "redux-thunk"; import logger from "redux-logger"; import rootReducer from "../reducers"; import * as localforage from "localforage"; offlineConfig.persistOptions = { storage: localforage }; // store offline data in indexedDB const store = createStore( rootReducer, {}, compose(applyMiddleware(thunk, logger), offline(customConfig)) ); export default store;
Now we will write a React component to interact with the redux store:
import React, { useState } from "react"; import { useSelector, useDispatch, shallowEqual } from "react-redux"; Import { addTodo } from "../redux/actions/app"; const ToDo = () => { const [todo, setTodo] = useState(""); const setTodoFunc = e => { dispatch(addTodo(todo)); }; // redux hook methods start const todoItemAdded = useSelector(state => { return { todoItem: state.app.todoItem, }; }, shallowEqual); const dispatch = useDispatch(); // redux hook methods end return ( <> <Input type="text" name="text" id="text" value={todo} onChange?={e => setTodo(e.target.value)} placeholder="Enter item name" /> <Button onClick?={setTodoFunc}>Add</Button> </> ); };
In redux/actions/app.js:
export const addTodo = content => ({ type: "ADD_TODO", payload: { content }, meta: { offline: { // the network action to execute: effect: { url: "/api/sample", method: "POST", body: `name=${content}`, headers: { "content-type": "application/x-www-form urlencoded" } }, // action to dispatch when effect succeeds: commit: { type: "ADD_TODO", meta: { content } }, // action to dispatch if network action fails permanently: rollback: { type: "ADD_TODO_ROLLBACK", meta: { content } } } } });
In redux/reducers/app.js:
const initialState = { id: "" }; export default (state = initialState, action) => { switch (action.type) { case "ADD_TODO": return (state = { ...state, todoItem: action.payload.content }); default: return state; } };
That’s it!
Now what will happen is when we click “Add” button after inserting an item name in text field which we created in React component, it will dispatch an action by making a POST HTTP request to our local API (“/api/sample”) using useDispatch hook which we have written in our action. Reducer will take initial state and based upon the action will return us the desired result which is our newly added item of-course.
The hook useSelector will get that updated value in our component.
Just to remind you that useSelector is an alternative to connect’s mapStateToProps. You pass it a function that takes the Redux store state and returns the state you need and useDispatch replaces connect’s mapDispatchToProps. All it does is return your store’s dispatch method so you can manually dispatch actions.
If somehow internet is disconnected or there is any network issue, magic will start then :)
It will try to hit our API (“/api/sample”) which of-course it won’t be able because of network disconnectivity, instead it will start interacting with indexedDB which we have already configured in our store and will keep trying to hit API meanwhile after specific intervals (we can set the interval also). All the data which was posted through POST HTTP request will be saved in indexedDB and as soon as network is back, it will move the data from indexedDB to our main DB and sync our main DB with the indexedDB.
End user will never get the impression that internet was disconnected and he can continue with his normal CRUD operations.
Isn’t it cool? It is actually.
An efficiently working offline-first application should consider aspects like:
- An attempt to resend the request when getting an incorrect reply from the server (for example, timeout)
- Sending requests only when we detect an internet connection or when our API is up
- Action queuing
- Persisting a queue of actions between the relaunch of the app
Redux offline is a complete solution, which implements the above functionalities and additionally allows you to configure:
- The time period between requests send to the server when resending requests
- The number of failed requests before rollbacking
- The library for handling those requests (for example, axios)
- Queue implementations
- Change the configurations
However, we need to remember that if the user is not able to open our page somehow, he will not be able to download all the logic and therefore the app will not be loaded. This is not the case in ReactNative where we have access to the application logic right after app opening.
I am sure you are fully equipped now to get your hands dirty in developing offline-first React application.
Did you learn something new today? Comments and feedback always make the writer happy!
About the Author:
Having 7+ years of professional software development experience in various cutting edge technologies, currently working as a Principal Software Engineer in a silicon valley based company in full stack capacity using ReactJs and Apollo-GraphQL. I love to write about technology and share my professional experience with everyone.
Entrepreneur
5 年Sharing it with our mobile app team ??
Strategic Lead | Senior Lecturer | Ex @King's | Co-Founder
5 年This is an excellent share. Can we integrate it with an ethereum wallet?