Refactoring HARA Mobile Redux State
Some of you might have already know that we are developing our mobile app in React Native. Why React Native, you ask? That will be a discussion for another day, since today we’re going to take a condescending look at our code, and get mad at our past self for writing such code.
It’s not that big of a mess, really. We’re talking about redux files on every container, coupled with their sagas, storing states for mostly just their respective container. Some of us might argue and not call it a bad practice. We’ve seen this practice everywhere, and they still works fine. But as we’re developing our app, we spot a pattern and figure out a way to refactor the code for more re-usability.
What is HARA?
To put things on perspective, we will first explain about our app. HARA Mobile is a mobile application for our Field Officer to capture farmer, land, and cultivation data out there in the field. Field Officers would go around their area to collect farmer’s data, including their phone numbers and some documents. They will then proceed to tag the farmer’s land using built in land-tagging feature from our app. After collecting the data, the Field Officer would maintain relationship with the farmer and collect their cultivation data, including planting, farm input, pest, disease, and harvest data periodically. Sometimes, they need to capture the data in an area with limited network coverage, so we need to implement offline capability in our app.
The Previous Structure
As we’ve already mentioned before, we’re having different redux and sagas for each of our container. While it’s quite easy to maintain the code that way, since what we need in our container would be stored in their own respective redux state, as the app grows larger we found some unnecessary data duplication, thus making it harder to maintain the data consistency. The addition of offline capability makes this problem even harder to cope.
You can see from the file structure above that each container will have an index.js
where the component is defined, a styles.js
to hold the stylings, a redux.js
for the types, actions, and reducers, and a sagas.js
for the sagas. Some of these containers might use the same data entities to display their content, each will be stored in their own state. For example, Container A needs a list of user to display something, and Container B needs another list of user for their own display. They will store each list of user in their own redux state, catered to their own usage. Maintaining consistency across lists would be a pain in the derriere, especially when we’re considering they will be persisted for offline usage.
The New Structure
So we’re taking a step back to look at our code, and then it hits us. Why don’t we take out the entities from the containers’ redux, and put it in another redux file for each entity? That way we can keep the data consistent across containers just by updating the entities once, while still accommodating each container’s list by creating an array for each container to store the id of the entities.
To implement this structure, first we need to identify the entities that we used in our app. This is important since each entity will have its own redux, and if an entity contains other entity, we need to normalize the entity and put the other entity in a different state. For this normalization, we are using normalizr
, which will be using schema files for each entity to define the relationship for the entities and normalizes the nested entities according to it.
We will then create a standard default state for each entity, which includes an object that contains the entities and some for each container that will hold the id of the entities the use. The list names will also be stored inside a variable to make it easier for us to get all the list stored there. The array will be dynamically created as containers request to fetch list of entities. The structure will roughly looks like this:
{
//The entities object
entities: {
1: {
//entity data
},
2: {
//entity data
},
....
},
//Array of list names
listNames: [
'main',
'searchResult',
...
],
//The list containing entity ids
main: [ 1,2,3,... ],
searchResult: [ 3,5,9,... ],
}
To access the list, we will provide selectors for each entity. This selector will denormalize the requested list and return it to the container. We’re using reselect
to create memoized selectors. The selectors won’t be recomputed unless one of its arguments changed. Here is an example of selector for a person entity with a region entity nested in it:
//get the entities objects
const getPersonEntities = state => state.person.entities;
const getRegionEntities = state => state.region.entities;//the selector function that will take current state and listName as an argument
export const getList = (state, listName) => {
//get the list, or use empty list if no such list exist
const list = state => state.person[listName] || []; //return the memoized selector
return createSelector(
//the inputSelectors
[list, getPersonEntities, getRegionEntities],
//the function that will be computed
(list, persons, regions) => {
const result = list.map((i) => {
const item = persons[i]; //check if there's an entity with the respective id
return
item ?
item.set("region", regions[item.region]) :
null;
});
return result;
}
)(state);
};
Another thing that we noticed by separating the entities is that most of their reducers are doing the same thing, which includes the modification of the entity objects and the list arrays, as well as the request handler for the CRUD. To make the reducers more reusable, we put the reducer functions in a separate file that will be imported in each entity redux file. Since we’re using reduxsauce
, hooking up the reducers to types is simplified with the createReducers
function. The actions are also created using the createActions
function, which will be connected to the types by changing the types’ name into camelCase. Here is the creation of a person entity’s reducers:
import {
addRequest,
addSuccess,
addFail,
editRequest,
editSuccess,
editFail,
fetchRequest,
fetchSuccess,
fetchFail,
deleteRequest,
deleteSuccess,
deleteFail,
loadEntities,
addEntity,
patchEntity,
removeEntity,
loadList,
appendList,
} from './EntityReducer';export const reducer = createReducer(INITIAL_STATE, {
//Handling add request
[Types.PERSON_ADD_REQUEST]: addRequest,
[Types.PERSON_ADD_SUCCESS]: addSuccess,
[Types.PERSON_ADD_FAIL]: addFail, //Handling edit request
[Types.PERSON_EDIT_REQUEST]: editRequest,
[Types.PERSON_EDIT_SUCCESS]: editSuccess,
[Types.PERSON_EDIT_FAIL]: editFail, //Handling fetch request
[Types.PERSON_FETCH_REQUEST]: fetchRequest,
[Types.PERSON_FETCH_SUCCESS]: fetchSuccess,
[Types.PERSON_FETCH_FAIL]: fetchFail, //Handling delete request
[Types.PERSON_DELETE_REQUEST]: deleteRequest,
[Types.PERSON_DELETE_SUCCESS]: deleteSuccess,
[Types.PERSON_DELETE_FAIL]: deleteFail, //Handling entity object modifications
[Types.PERSON_LOAD_ENTITIES]: loadEntities,
[Types.PERSON_ADD_ENTITY]: addEntity,
[Types.PERSON_PATCH_ENTITY]: patchEntity,
[Types.PERSON_REMOVE_ENTITY]: removeEntity, //Handling list array modifications
[Types.PERSON_LOAD_LIST]: loadList,
[Types.PERSON_APPEND_LIST]: appendList,
});
By using such structure, we only need to maintain a single source of data for each entity. The modification of entity will be much simpler, since we can create, edit, and delete a single entity data and there’s no need to reload the list afterwards. It also make persisting states simpler, since this would avoid data redundancy, thus reducing the storage size needed. We’ve also added an offline search feature recently, where user can search the data even if they have no internet connection. It will only search from persisted state that they have fetched before. Since all the entities for that list are stored in a single object, we only need to search within that object to yield the result.
What now?
That’s pretty much what we did to refactor our old redux state. Users don’t really feel much difference, but it helps us a lot in the future development. Now almost all the redux and sagas are separated from the container, and the code looks cleaner. So here’s to summarize what we did:
- We’ve been using redux and sagas inside the containers. This might not be a bad practice per se, but we think we need to restructure it to work better with our app’s offline capability.
- We are restructuring our state by classifying them into entities and create a schema for each entity’s relation.
- The state is stored in a normalized form. To get the list of the entities, we are using memoized selectors.
- Since most entities have similar reducers to manipulate their contents, we are making generic reducers to improve code reusability and to ease the maintenance.
- By using this structure we’re reducing data redundancy and increasing data consistency. This also works well with our offline capability and reduce the size of offline data stored in client’s device.
There’s always room for improvement though, since our code is still miles away from the perfect code. One of the next improvement we will be doing might be replacing our current form state management library. We’re currently using Redux Form, but we think we don’t really need redux for such thing, right? So we are looking forward to our future improvement, replacing Redux Form with Formik. There are pros and cons on this, so keep an eye on us for our next article!
Okay, so here’s the end of this article (phew, finally~). Thanks for tuning in! I’m not a really good writer so I might miss something here and there. If you have any discussions, suggestions, questions, critics, or even bashes, please leave a comment. Adios!