Modular Store
There are 6 modules:
Theme
Stores a single value: user’s theme
, which sets dark mode
.
Pricing
Stores the selected product plan
, billing period
and currency
, as well as the user’s current plan
.
Auth
Stores an authenticated
flag and the user’s JSON Web Token
.
Account
Stores the user
state (email, first name, last name…).
Tenant
Stores the current tenant
and the current user workspace
.
App
Stores the current plan features
with their limits, and the current user usage
.
Persited
Every store is saved in the client’s localStorage, using different methods for each framework/library.
Store
Vue2
Uses vuex and vuex-persistedstate.
import Vue from "vue";
import Vuex, { StoreOptions } from "vuex";
import createLogger from "vuex/dist/logger";
import createPersistedState from "vuex-persistedstate";
// Modules
import { theme } from "./modules/themeState";
import { pricing } from "./modules/pricingState";
import { auth } from "./modules/authState";
import { app } from "./modules/appState";
import { account } from "./modules/accountState";
import { tenant } from "./modules/tenantState";
import { RootState } from "./types";
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== "production";
const store: StoreOptions<RootState> = {
modules: {
account,
auth,
tenant,
pricing,
theme,
app,
},
strict: debug,
plugins: debug ? [createLogger(), createPersistedState()] : [createPersistedState()],
};
export default new Vuex.Store<RootState>(store);
Vue3
Uses the latest release of vuex and vuex-persistedstate.
import { createStore, createLogger } from "vuex";
import createPersistedState from "vuex-persistedstate";
// Modules
import { account } from "./modules/accountState";
import { auth } from "./modules/authState";
import { tenant } from "./modules/tenantState";
import { pricing } from "./modules/pricingState";
import { theme } from "./modules/themeState";
import { app } from "./modules/appState";
import { RootState } from "@/store/types";
const debug = import.meta.env.NODE_ENV !== "production";
export const store = createStore<RootState>({
modules: {
account,
auth,
tenant,
pricing,
theme,
app,
},
strict: debug,
plugins: debug ? [createPersistedState(), createLogger()] : [createPersistedState()],
});
export default store;
React
Uses @reduxjs/toolkit to combine reducers.
import { combineReducers, createStore } from "@reduxjs/toolkit";
import { loadState, saveState } from "./localStorage";
import themeReducer from "./modules/themeReducer";
import authReducer from "./modules/authReducer";
import throttle from "lodash.throttle";
import accountReducer from "./modules/accountReducer";
import tenantReducer from "./modules/tenantReducer";
import pricingReducer from "./modules/pricingReducer";
import appReducer from "./modules/appReducer";
const reducers = combineReducers({
account: accountReducer,
auth: authReducer,
tenant: tenantReducer,
pricing: pricingReducer,
theme: themeReducer,
app: appReducer,
});
const persistedState = loadState();
const store = createStore(reducers, persistedState);
store.subscribe(
throttle(() => {
saveState({
account: store.getState().account,
auth: store.getState().auth,
tenant: store.getState().tenant,
pricing: store.getState().pricing,
theme: store.getState().theme,
app: store.getState().app,
});
}, 1000)
);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
Svelte
Uses svelte’s own store manager svelte/store.
import { accountStore } from "./modules/accountStore";
import { appStore } from "./modules/appStore";
import { authStore } from "./modules/authStore";
import { pricingStore } from "./modules/pricingStore";
import { tenantStore } from "./modules/tenantStore";
import { themeStore } from "./modules/themeStore";
const store = {
account: accountStore,
auth: authStore,
tenant: tenantStore,
pricing: pricingStore,
theme: themeStore,
app: appStore,
};
export default store;
Theme State Samples
Vue2
State
import { Module } from "vuex";
import { Theme } from "@/application/enums/shared/Theme";
import { RootState, ThemeState } from "@/store/types";
const initialState: ThemeState = {
theme: Theme.LIGHT,
};
export const theme: Module<ThemeState, RootState> = {
namespaced: true,
state: initialState,
mutations: {
reset(state: ThemeState) {
state.theme = initialState.theme;
const htmlClasses = document.querySelector("html")?.classList;
htmlClasses?.remove("dark");
},
setTheme(state: ThemeState, payload: number) {
state.theme = payload;
const htmlClasses = document.querySelector("html")?.classList;
if (payload === 0) {
htmlClasses?.remove("dark");
} else {
htmlClasses?.add("dark");
}
},
},
};
Usage
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
import store from "@/store";
import { Theme } from "@/application/enums/shared/Theme";
@Component({})
export default class DarkModeToggle extends Vue {
toggle() {
if (this.currentTheme === 1) {
store.commit("theme/setTheme", Theme.LIGHT);
} else {
store.commit("theme/setTheme", Theme.DARK);
}
const htmlClasses = document.querySelector("html")?.classList;
if (this.currentTheme === 0) {
htmlClasses?.remove("dark");
} else {
htmlClasses?.add("dark");
}
}
get currentTheme() {
return store.state.theme.theme;
}
}
</script>
Vue3
State
import { Module } from "vuex";
import { Theme } from "@/application/enums/shared/Theme";
import { RootState, ThemeState } from "@/store/types";
const initialState: ThemeState = {
theme: Theme.LIGHT,
};
export const theme: Module<ThemeState, RootState> = {
namespaced: true,
state: initialState,
mutations: {
reset(state: ThemeState) {
state.theme = initialState.theme;
const htmlClasses = document.querySelector("html")?.classList;
htmlClasses?.remove("dark");
},
setTheme(state: ThemeState, payload: number) {
state.theme = payload;
const htmlClasses = document.querySelector("html")?.classList;
if (payload === 0) {
htmlClasses?.remove("dark");
} else {
htmlClasses?.add("dark");
}
},
},
};
Usage
<script setup lang="ts">
import store from "@/store";
import { Theme } from "@/application/enums/shared/Theme";
import { computed } from "vue";
function toggle() {
if (currentTheme.value === 1) {
store.commit("theme/setTheme", Theme.LIGHT);
} else {
store.commit("theme/setTheme", Theme.DARK);
}
const htmlClasses = document.querySelector("html")?.classList;
if (currentTheme.value === 0) {
htmlClasses?.remove("dark");
} else {
htmlClasses?.add("dark");
}
}
const currentTheme = computed(() => {
return store.state.theme.theme;
})
</script>
React
import { Theme } from "@/application/enums/shared/Theme";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { ThemeState } from "../types";
const value = JSON.stringify(localStorage.getItem("theme"));
const initialState: ThemeState = {
value: value ? Number(value) : Theme.LIGHT,
};
export const themeSlice = createSlice({
name: "theme",
initialState,
reducers: {
hydrate: (state, action) => {
// do not do state = action.payload it will not update the store
return action.payload;
},
setTheme: (state, { payload }: PayloadAction<Theme>) => {
state.value = payload;
localStorage.setItem("theme", JSON.stringify(state.value));
},
},
});
export const { setTheme, hydrate } = themeSlice.actions;
export default themeSlice.reducer;
Usage
import { Theme } from "@/application/enums/shared/Theme";
import store, { RootState } from "@/store";
import { setTheme } from "@/store/modules/themeReducer";
import { useSelector } from "react-redux";
export default function Header() {
const theme = useSelector<RootState>((state) => state.theme.value);
const toggle = () => {
const htmlClasses = document.querySelector("html")?.classList;
if (theme === Theme.DARK) {
store.dispatch(setTheme(Theme.LIGHT));
htmlClasses?.remove("dark");
} else {
store.dispatch(setTheme(Theme.DARK));
htmlClasses?.add("dark");
}
};
return (
...
Svelte
State
import { writable } from "svelte/store";
import { Theme } from "../../application/enums/shared/Theme";
import type { ThemeState } from "../types";
const initialState: ThemeState = JSON.parse(localStorage.getItem("theme") ?? "{}") ?? {
value: Theme.LIGHT,
};
export const themeState = writable(initialState);
export const themeStore = {
setTheme: (value: Theme) =>
themeState.update((self) => {
self.value = value;
return self;
}),
};
themeState.subscribe((val) => {
localStorage.setItem("theme", JSON.stringify(val));
});
Usage
<script lang="ts">
import { Theme } from "@/application/enums/shared/Theme";
import { themeState, themeStore } from "@/store/modules/themeStore";
$: theme = $themeState.value;
function toggle() {
const htmlClasses = document.querySelector("html")?.classList;
if (theme === Theme.DARK) {
themeStore.setTheme(Theme.LIGHT);
htmlClasses?.remove("dark");
} else {
themeStore.setTheme(Theme.DARK);
htmlClasses?.add("dark");
}
}
</script>