In part 1, we learned how to cache static files for our app shell using Service Worker, so that our React app is available offline (partially). In-case you haven’t read part 1, please have a quick glance at it, as it points to some really nice links to learn basics on PWA.
What we learn in part 2
We will learn how to create a Offline first React-Redux app using PWA concepts + persisting the Redux Store. We will see this in detail in the subsequent sections.
Well a video can explain a lot more than writing.
The below three steps is what would be required as a base to create our React-Redux Offline first app using PWA (apart from manifest.json/service workers etc which I have already mentioned in part 1.)
For persisting the Redux actions in memory and to store the Redux store in localStorage, I have build my own code mainly because it helps to learn and understand. And this is exactly why I haven’t gone and used any npm package to achieve this. Although I must add that when I was stuck and needed to understand how to persist async actions I did go through Redux-Persist.
To summarise, Part 2 is going to be a bit more complex where
- Service Worker — Static files would be cached using Cache First else fall-over to network strategy. (This is what we did for the app shell in part 1)
- Service Worker — Api data which are required from the server (GET method to get list of all the items in TODO) will be cached with a network first and fall-over to Cache strategy.
- Redux Offline first thinking — Our application’s state:
- Point 1 & 2 makes use of concepts from PWA to cache important api/files to make them available offline using different strategies.
- Point 3 covers more on persisting the Redux Store so that next time when you are online/restart the app, the state is maintained.
- The Redux Actions — Persisted to in-memory and therefore will be erased on refreshing the app or closing and revisiting the app later.
- Redux Store — Persisted to local-storage and therefore will be available even on refresh or closing and revisiting the app later.
Why am I not persisting Actions onto localStorage or IndexedDb? Well it was to just keep it simple. I wanted to use localForage and store the actions on IndexedDb, but then I was already pressed for time, and I felt for now, there is already lot of stuff that will be covered in part-2 and it was best to leave it simple.
Now let us see these three points in detail.
1. Service Worker — Static Assets
Static assets don’t change that often, and therefore it can go through the strategy of Cache First and fall over to Network
2. Service Worker — APIs to GET data from the server
In our case, we have only one GET request which is called at the time of loading the page to get list of all the TODOs.
For APIs, we would prefer to get the latest value always, but incase there is a network failure or we are offline, we don’t want the app to break but let us continue. This could be done using the NETWORK first then CACHE strategy where, if network request fails, we send the data from Cache so that rather than failing, we have the previous version of data. Else we update the Cache with the latest Network response and send that.
Redux Actions and Store
This is the more complex part. This is where we will control the entire state of our React-Redux app.
Think of this as a jigsaw puzzle where one piece doesn’t make sense, but fit everything together and it makes perfect sense :-)
The below steps are important to think of an offline first approach for our Redux app.
- Detect when the network state changes
- When offline, save the async actions to be processed later
- When online, process all the async actions which have been queued.
- Persist the store and re-hydrate the store when required.
Detect if we are online/offline
By adding event listeners as shown here. Yup it’s that simple :-)
Custom Redux Enhancer to achieve the offline first approach
I am creating a custom enhancer as shown below which:
- Has an array to store the Actions in memory. (You can change it to persist the data on local storage or maybe IndexedDb. I just wanted to keep it basic for now)
- Checks for Action “YOU_ARE_ONLINE”, and process all the actions which have been queued up. (Notice the event listener on online which dispatches this action).
- Save all the Actions that needs to be processed later (“PROCESS_WHEN_ONLINE”)
Now let us see how the Redux async actions are handled.
Saving the Redux Actions for later
- If you see the below async action, you can see that we don’t directly make the fetch request as we traditionally do, but build an object with required information.
- If we are online, then the request is processed using the object details immediately.
- If we are offline, then the request is saved for later, and processed when we are online next.
That’s all there is to saving the action for later. This is where I had to look at the Redux-Persist library to get this approach because if we try to save an async action which is written the way we usually do, it ends up with various issues, eg. it’s just a normal function after all and you can’t persist a function as you lose it’s scope to start with.
I think this is where I took the most time to understand how to persist the async actions.
Persisting the Redux Store
In this example, I am persisting the store after dispatch of each action. You can modify in your fork/branch to persist the store only at certain necessary occasions.
For this, the easiest choice was to use store.subscribe as show below :-)
Not the best idea to persist for every action, but would be better to persist at set intervals. (Something similar to checkpoint commit in database if you know what I mean).
Code Repository and setting up
I just wrapped up something quick, and there is a lot of scope for enhancement.
You can find the code repo here:-
For this article, use the part2 branch and not the master.
After cloning go to the folder react_redux_pwa branch and install all the dependencies
yarn install (OR)npm i
Setting up MongoDb
You will have to setup a node with express and mongodb server for the backend.
If you have never used MongoDb and set it with Node and Express, you can see follow the steps to set up MongoDb by referring to this article.
Setting up the Server
If you get the MongoDb setup done, then this is pretty straight forward. And just have to run the below command.
yarn server:dev(OR)npm run server:dev
This will bring up the server on http://localhost:3000
Setting up the Client
Run the command
yarn start(OR)npm run start
This will bring up the dev server on http://localhost:8080
Now you can access the client on your browser at localhost:8080, or if you have read my part-1 document, then you know how to do port forwarding and access your chrome page from your mobile.
Although this is not a perfect solution, I was obsessed on trying to build something on my own so that I could understand and think better on how an offline first React-Redux app should be designed.
And therefore the intention of this article is to help you understand how to create an offline first React-Redux app using the principles of PWA and persisting and re-hydrating the Redux store.
This solution may not suit to be used for a Production atleast not in its current form. Once you understand the concepts, you can choose Redux-Persist, or Redux-Offline, which uses Redux-Persist, but adds much more functionality and kind of handles almost all the scenarios required for handling offline.
The authors of these two packages have done really awesome job!
And I would highly recommend you to try them out once you get a fair understanding on how things fall in place.
Hope you guys get a better understanding on how to create an offline first React-Redux app :-)
Until my next article…..
Happy Learning!! 🖒