This week concludes the last week of my internship at 9cv9 as part of the NUS Overseas College (NOC) Southeast Asia programme. Continuing from last week’s reflection, before the internship ends, I decided to look into parts of the codebase for UrbanCV that I did not touch before, as I was curious about how those features were implemented to try and understand it by reading through the code as well as reading online resources.This week I will be looking into how UrbanCV persists its redux store through refresh, and two interesting features used I did not know about previously, Event Bus and Debounce.
Redux Persist is a library that persists and rehydrates a redux store enabling the redux store’s data to be persisted across refreshes as it saves the redux store in the local storage of the browser. With Redux Persist, you can choose how many levels of state to merge, ie. to use the data entirely from the persisted store, or to just use some of it. A brief explanation of how it works is through 3 steps. INIT, where the redux store is created with the usual initial state. PRESTIST, where the stores that were persisted get an object {version: -1, rehydrated: false} added. Finally the last step, REHYDRATE involves checking the rehydrated boolean from earlier and replaces the data in the redux store with the persisted data stored in the browser’s local storage. Here is a simple example of how to use redux-persist to create a persistent store. To use this store, simply wrap a PersistGate component around the main React app.
import { createStore } from ‘redux’
import { persistStore, persistReducer } from ‘redux-persist’
import storage from ‘redux-persist/lib/storage’ // defaults to localStorage for web
import rootReducer from ‘./reducers’
const persistConfig = {
key: ‘root’,
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}
Event Bus (sometimes also known as publish/subscribe or pubsub) is a design pattern that can be used to simplify communications between different components. The different components listen to the event bus to know when to do what they need to do. An example of a simple event bus can be written like this:
const eventBus = {
on(event, callback) {
document.addEventListener(event, (e) => callback(e.detail));
},
dispatch(event, data = {}) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event, callback) {
document.removeEventListener(event, callback);
},
};
An example of how this is used in UrbanCV is when the user’s login session has timed out, to clear the data stored in the browser and log them out. This can be done by having the main App listen for the ‘logout’ event and dispatch the logout action when it occurs.
eventBus.on(‘logout’, () => store.dispatch(logout()));
When the user tries to make a request and the token is expired, we can then dispatch the ‘logout’ event to log the user out and prompt then to login again.
eventBus.dispatch(‘logout’);
The last item of interest is Debounce. Debounce is a higher-order function, and allows you to group multiple sequential calls in a single one. This is useful in instances such as when a user types in an input and it appears on screen. By using a debounce function to delay the processing of the keys typed until the user has stopped typing for a predetermined time, you can prevent the User Interface from having to process every event and constantly update/make api calls. This is also used in UrbanCV, where we add debounce functions to prevent having to constantly update the preview of the CV, when the user types and updates the value of certain fields. Luckily this useful function can be easily added to your application by installing lodash.debounce, and wrapping your functions used to update the state with it. An example of wrapping the useState hook to only update the state once a period of 500mswithout the user typing has passed is:
const updateCV = useCallback(
debounce((newValues) => {
handleSaveData(‘basicInfo’, newValues, false);
}, 500),
[],
);
In the above, useCallback will return a memoized version of the debounce callback that only changes if one of the dependencies has changed (which in this case, there are no dependencies).