State Management
State management for CJjs apps
State Management Architecture
The library divides state management into three clearly differentiated concepts:
1. Store
Central and unique point containing all the application’s state configuration. It is responsible for registering and organizing the different slices corresponding to each stage of the sales funnel.
2. Slices
Modular and self-contained units of state. Each slice defines:
- Initial state
- Reducers / state update functions (action → new state) These represent logical portions of the state, generally associated with a page or stage of the funnel.
3. Updaters
Reactive layer / side effects layer. These are functions that subscribe to specific state changes (precise selectors). Their main responsibility is to react to detected changes and perform the following actions:
- Update the user interface
- Execute transitions between pages
- Trigger side effects (save data, send events, analytics, etc.)
1. Store
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import contextSlice from "./slices/contextSlice";
import homeSlice from "./slices/homeSlice";
import byeSlice from "./slices/byeSlice";
/**
* Configure the Redux store with slices and persistence.
*/
const persistConfig = {
key: 'root',
storage
};
/**
* Combine the slices into a root reducer.
*/
const rootReducer = combineReducers({
context: contextSlice,
home: homeSlice,
bye: byeSlice
});
/**
* Create a persisted reducer using the root reducer and persistence configuration.
*/
const persistedReducer = persistReducer(persistConfig, rootReducer);
/**
* Configure the Redux store with the persisted reducer.
*/
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
/**
* Create a persistor to manage the persistence of the store.
*/
const persistor = persistStore(store);
export { store, persistor };
2. Slices
import { createSlice } from '@reduxjs/toolkit';
/**
* Home slice to manage the state of the home component, including stage and scroll stoping.
*/
const homeSlice = createSlice({
name: 'home',
initialState:{
stage:'awaiting',
scrollStopping:{
page:{
req:{},
name:'',
session:'',
start:0,
end:0,
time:0,
leavingApp:0,
views:0
},
}
},
reducers:{
setStage:(state, action) => {
state.stage = action.payload;
},
setScrollStopping:(state, action) => {
state.scrollStopping = action.payload;
},
setSectionTracking:(state, action) => {
let section = Object.keys(action.payload)[0];
state.scrollStopping.sections[section] = action.payload[section];
},
setEscapeAttempt:(state, action) => {
state.scrollStopping.page.leavingapp = action.payload;
},
setPageQuit:(state, action) => {
state.scrollStopping.page = action.payload;
}
}
});
export const { setStage, setScrollStopping, setSectionTracking, setEscapeAttempt, setPageQuit } = homeSlice.actions;
export default homeSlice.reducer;
3. Updaters
import { store } from "../../store/store";
import { setSectionTracking, setEscapeAttempt, setPageQuit } from "../../store/slices/homeSlice";
/**
* Manage changes in the home page state
* @param {object} previousState
* @param {object} currentState
*/
export function homeUpdater(previousState, currentState){
/**
* Page instance
* @type {object}
*/
let page = document.querySelector('app-page');
/**
* If there are changes in language or theme, update the context and reload data.
* If there are changes in the home stage, update the appoinment component accordingly.
*/
if (previousState.context.lang!=currentState.context.lang||previousState.context.theme!=currentState.context.theme){
page.data.context = currentState.context;
page.loadData();
}else if(previousState.home.stage!=currentState.home.stage){
let track = currentState.home.scrollStopping;
let payload = {};
console.log(`Home stage changed to ${currentState.home.stage}`);
switch (true){
case currentState.home.stage === 'attention/click':
document.getElementById("action").scrollIntoView({ behavior: "smooth"});
break;
case currentState.home.stage.startsWith('action/click-'):
payload = page.setPageQuit(track.page);
store.dispatch(setPageQuit(payload));
window.location.href = `/#thanks?product=${currentState.home.stage.match(/click-([\w-]+)$/)?.[1]}`
break;
case currentState.home.stage === 'atenttion/viewed':
payload = page.setSectionViewed('attention',track.sections.attention);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'interest/viewed':
payload = page.setSectionViewed('interest',track.sections.interest);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'desire/viewed':
payload = page.setSectionViewed('desire',track.sections.desire);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'action/viewed':
payload = page.setSectionViewed('action',track.sections.action);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'attention/unviewed':
payloasd = page.setSectionUnviewed('attention',track.sections.attention);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'interest/unviewed':
payload = page.setSectionUnviewed('interest',track.sections.interest);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'desire/unviewed':
payload = page.setSectionUnviewed('desire',track.sections.desire);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'action/unviewed':
payload = page.setSectionUnviewed('action',track.sections.action);
store.dispatch(setSectionTracking(payload));
break;
case currentState.home.stage === 'escape':
let leavingApp = track.page.leavingapp + 1;
store.dispatch(setEscapeAttempt(leavingApp));
if (leavingApp===1){
document.getElementById("message").setAttribute("active", "")
}
break;
case currentState.home.stage === 'quit':
payload = page.setPageQuit(track.page);
store.dispatch(setPageQuit(payload));
break;
}
}
}