Skip to content

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 by getUserData action; 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 api module. Many of its actions (getMyPosts, getStatusMyPosts) overlap with newer cached actions in store/api.js. Prefer the api-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:

this.$store.dispatch('api/clearApiCache')   // wipes every cache in the module

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 that api.js uses everywhere else. If you copy the pattern, copy the constant too.
  • The action also writes cookies (onboardingFlow, onboardingtype) so that mixins/onboardingNavigation.js and 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

  1. Cache-first reads. When you need data the user has plausibly seen recently, dispatch the cached action first. Only call $axios directly when the endpoint isn't wrapped in api.js and you don't want to add a wrapper.
  2. 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 — copy fetchUserDetails and substitute the endpoint and cache name.
  3. Always invalidate on mutation. A POST/PUT/DELETE that changes server state should be paired with either a force: true refetch or a clearApiCache.
  4. Return values from actions, don't rely on getters firing reactively. The pattern in api.js is return await dispatch(...). Components await the dispatch and consume the return value directly, then optionally refresh from the getter on subsequent renders.
  5. 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 = bar will pass review but is brittle.

Patterns to avoid

  • Reading from the root user state. Use app.$auth.user or api/getUserDetails instead.
  • Hardcoding account_id in payloads. Always read it from this.$auth.user.accountId or this.$cookies.get('accountId'). The first should be preferred inside components; the second is fine in middleware where $auth may not yet be initialised.
  • Adding a third overlapping cache. Before adding state to a module, check whether api.js already wraps the endpoint you're about to call.
  • Manually clearing only some api caches. Either force: true the specific actions you mutated, or clearApiCache everything. Partial manual clears get out of sync.