05 — State management¶
This codebase uses Vuex 3 (the Vue 2 generation of Vuex). The single most important thing to understand is the api module, which is a 5-minute cache layer in front of the most-fetched backend endpoints. Almost every component in the app depends on it directly or transitively.
Modules at a glance¶
store/
├── index.js ← root module (non-strict; export const strict = false)
├── api.js ← central caching layer (this is where you usually want to look first)
├── onboarding.js ← onboarding flow state
├── chat/ ← actions/getters/mutations/state
├── common/ ← actions/getters/mutations/state
├── dashboard/ ← actions/getters/mutations/state
├── leaderboard/ ← actions/getters/mutations/state
├── plan/ ← actions/getters/mutations/state
├── post/ ← actions/getters/mutations/state
├── reach/ ← actions/getters/mutations/state
└── modules/
└── sitemap.js
Nuxt auto-registers each top-level file/folder as a Vuex module. So the api module is reached via store.dispatch('api/fetchSubscription'), store.getters['api/getSubscriptionData'], and so on.
store/index.js is the root module. It is non-strict (export const strict = false) — direct mutation outside actions/mutations does not throw, but you should not rely on this. Treat the codebase as if strict mode were on.
The root module (store/index.js)¶
The root module holds:
user— populated bygetUserDataaction; rarely set in practice.userAccounts,selectedAccount,isAccountSwitching— the account-switcher state.companyLogo,userDb,userSetting,defaultAccount,defaultCategoriess,industrylist,categorylist— assorted UI state.myPosts,myStatusPosts,postArray,postDataSet,plans— pre-API-module post-listing state.pageTitle,pageDescription,sidebarVisible,userBlockVisible— chrome state.socialdata,sociallinkdata— social-account linkage data.
It also defines login/social-login actions (getUserData, loginuser, socialRegister, linkedInLogin, twitterLogin, twitterRLAuth), industry/category fetchers, and post-listing fetchers.
The root module pre-dates the
apimodule. Many of its actions (getMyPosts,getStatusMyPosts) overlap with newer cached actions instore/api.js. Prefer theapi-module versions for new code — they are cached and have a consistent shape. Do not delete the root-module ones without checking call sites; many components still use them.
The api module — read this carefully¶
store/api.js wraps a fixed list of backend endpoints in cache-with-TTL actions. Default TTL is 5 minutes (CACHE_DURATION = 5 * 60 * 1000). The module is the de-facto standard pattern for any feature that needs to read data from the backend.
Single-value cached actions¶
| Action | Endpoint | Cache state |
|---|---|---|
api/fetchSubscription |
GET /auth/getSubscriptions |
subscriptionCache / lastSubscriptionFetch |
api/fetchUserDetails |
GET /auth/getUserdetail |
userDetailsCache |
api/fetchLanguages |
GET /getLanguages |
languagesCache |
api/fetchToneOfVoice |
GET /getToneOfVoice |
toneofVoiceCache |
api/fetchSettings |
GET /auth/getSetting |
settingsCache |
api/fetchDIYPost |
GET /getDiy_text |
diyPostCache |
api/fetchAllReelIdeas |
GET /auth/getAllReelIdeasFolder |
allReelIdeasCache |
api/fetchAccountDetails |
POST /auth/getaccountdetails |
accountDetailsCache |
api/fetchApprovedDates |
GET /auth/getApprovedDates |
approvedDatesCache |
api/fetchLibraryPosts |
GET /auth/getAllLibrary |
libraryCache |
Calling pattern:
// Component method
async loadStuff() {
this.subscription = await this.$store.dispatch('api/fetchSubscription')
this.accountDetail = await this.$store.dispatch('api/fetchAccountDetails')
}
// Force a refetch (skip cache)
const fresh = await this.$store.dispatch('api/fetchSettings', { force: true })
Each action returns the cached value if Date.now() - lastFetch < CACHE_DURATION and force is not set. On error, the actions return the previously cached value as a fallback (silent failure).
Paginated cached actions¶
| Action | Endpoint | Cache state (per page) |
|---|---|---|
api/fetchPosts |
POST /auth/getpost |
postsCache[page] / postsRows[page] |
api/fetchFavPosts |
POST /auth/getfavouriteposts |
favPostsCache[page] / favPostsRows[page] |
api/fetchSharedImages |
POST /auth/getAllSharedImages |
sharedImagesCache[page] / sharedImagesRows[page] |
api/fetchImageLibrary |
GET /auth/getMyLibraryPost/<page> |
imageLibraryCache[page] / imageLibraryRows[page] |
Calling pattern:
const page1 = await this.$store.dispatch('api/fetchPosts', { page: 1 })
const total = this.$store.getters['api/getPostsRows'](1)
// Force a refetch
await this.$store.dispatch('api/fetchPosts', { page: 1, force: true })
The getters for paginated caches are functions, not values. Note store.getters['api/getPosts'](page) — invoke it with a page number.
Cache invalidation¶
When you mutate data on the backend, you are responsible for invalidating the relevant cache so users see fresh data within the same session:
// After a POST that changes settings
await this.$axios.post('/auth/addOrUpdateSetting', payload)
await this.$store.dispatch('api/fetchSettings', { force: true })
There is also a nuclear option:
Use clearApiCache on logout, account switch, or when you cannot easily enumerate which caches the mutation invalidated.
Side effect to know about¶
fetchSubscription writes a cookie (subs = { sub_id }) as a side effect of caching. Other consumers (notably middleware/redirect.js) read that cookie to make routing decisions. Don't accidentally skip the dispatch by reading the cache directly — the cookie write only happens through the action.
Known papercut¶
In fetchImageLibrary, the account_Id header is read as this.accountId rather than this.$auth.user.accountId. Verify with the team whether this is a bug or there's an outer context that supplies this.accountId — most other paginated actions read from $auth.user. If you call this action from a component that doesn't satisfy that, the header may be undefined.
Other modules¶
post/, dashboard/, chat/, leaderboard/, plan/, reach/, common/ follow the same shape: separate actions.js, getters.js, mutations.js, state.js files. They typically own state that's specific to a single feature surface (e.g. post/ holds myUploadPost, myVideoPost, etc.).
onboarding.js is a single-file module holding the onboarding flow state. The flow is fetched by onboarding/fetchOnboardingFlow, which is dispatched from middleware/redirect.js immediately before the user is sent into onboarding. Pre-fetching is intentional — the first onboarding page does not need to refetch.
Two onboarding-specific things to know:
- The cache TTL is 3 minutes for
onboarding/fetchOnboardingDetails(ONBOARDING_DETAILS_CACHE_TTL), not the 5 minutes thatapi.jsuses everywhere else. If you copy the pattern, copy the constant too. - The action also writes cookies (
onboardingFlow,onboardingtype) so thatmixins/onboardingNavigation.jsand any code reading those cookies stays in sync. Don't bypass the action; you'll desync the cookies.
See 15-onboarding-flow.md for the full flow.
modules/sitemap.js is loaded specifically for sitemap generation; you should not need to interact with it in normal feature work.
Patterns to follow¶
- Cache-first reads. When you need data the user has plausibly seen recently, dispatch the cached action first. Only call
$axiosdirectly when the endpoint isn't wrapped inapi.jsand you don't want to add a wrapper. - Add a wrapper if you call something twice. If two components fetch the same endpoint within 5 minutes, add it to
api.js. The pattern to follow is mechanical — copyfetchUserDetailsand substitute the endpoint and cache name. - Always invalidate on mutation. A POST/PUT/DELETE that changes server state should be paired with either a
force: truerefetch or aclearApiCache. - Return values from actions, don't rely on getters firing reactively. The pattern in
api.jsisreturn await dispatch(...). Components await the dispatch and consume the return value directly, then optionally refresh from the getter on subsequent renders. - Don't mutate state from outside an action. Even though strict mode is off, the codebase's convention is mutations + actions only. Direct
this.$store.state.foo = barwill pass review but is brittle.
Patterns to avoid¶
- Reading from the root
userstate. Useapp.$auth.userorapi/getUserDetailsinstead. - Hardcoding
account_idin payloads. Always read it fromthis.$auth.user.accountIdorthis.$cookies.get('accountId'). The first should be preferred inside components; the second is fine in middleware where$authmay not yet be initialised. - Adding a third overlapping cache. Before adding state to a module, check whether
api.jsalready wraps the endpoint you're about to call. - Manually clearing only some
apicaches. Eitherforce: truethe specific actions you mutated, orclearApiCacheeverything. Partial manual clears get out of sync.