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>
