Simple Vuex plugin for serializing and deduplicating async actions
Prevents duplicate API calls · Clean logs · Zero configuration overhead
npm i @iits-consulting/vuex-mutex
# vuex must already be installed in your app// Deduplicate in-flight actions across Vuex namespaces
store.dispatch('user/fetchProfile')
store.dispatch('user/fetchProfile') // ← second call is droppedimport { createStore } from 'vuex'
import { createVuexMutexPlugin } from '@iits-consulting/vuex-mutex'
const store = createStore({
plugins: [
createVuexMutexPlugin({
include: [/^user\//],
dedupe: { inFlight: 'share', quickRepeat: 'drop', thresholdMillis: 500 },
isProduction: process.env.NODE_ENV === 'production',
debug: false,
}),
],
})| Option | Description |
|---|---|
| include / exclude | Filter by action type or RegExp |
| dedupe.inFlight | 'share' | 'drop' | 'warn' | 'block' |
| dedupe.quickRepeat | Same, applies after recent finish |
| thresholdMillis | Quick-repeat window (default 500 ms) |
| debug | Enable verbose console logs |
| isProduction | Mutes dedupe logs when true |
The plugin applies dedupe in two phases:
- In-Flight — while an identical action (same namespace + action + payload) is queued or running.
- Quick-Repeat — for a short time after the last identical action finished (configurable via
thresholdMillis, default500ms).
Two dispatches are considered identical if their
namespace/actionand a stable-JSON of the payload match.
| Mode | In-Flight (existing promise running) | Quick-Repeat (no promise; within thresholdMillis) |
|---|---|---|
share |
Re-use the ongoing Promise → caller awaits same result | Acts like drop (no promise to share) → returns undefined |
drop |
Ignore duplicate → returns undefined |
Ignore duplicate → returns undefined |
warn |
Start another execution and console.warn |
Start another execution and console.warn |
block |
Throw Error → prevents duplicate from starting | Throw Error |
Return values
share(in-flight): returns the same Promise as the first execution.drop/share(quick-repeat): returnsundefined.warn: returns a new Promise (another execution).block: throws.
dispatch #1 ──────── running ──────── done (t0)
▲ In-Flight ▼
dispatch #2 (same key) during #1 → In-Flight mode applies
dispatch #3 (same key) after t0, within thresholdMillis → Quick-Repeat mode applies
-
API calls (safe default): dedupe: { inFlight: 'share', quickRepeat: 'drop', thresholdMillis: 400 }
Reuse ongoing network requests while they’re in-flight, and ignore rapid re-clicks afterward.
-
Form submissions (strict double-click prevention): dedupe: { inFlight: 'block', quickRepeat: 'block', thresholdMillis: 800 }
Prevents any concurrent or rapid repeat submissions — suitable for “Save”, “Submit”, or destructive actions.
-
Development diagnostics: dedupe: { inFlight: 'warn', quickRepeat: 'warn' } // defaults debug: true
Allows duplicates but logs every event; useful for debugging or timing analysis.
Tip: Adjust thresholdMillis (default 500 ms) to control how long after an action finishes a quick repeat is still deduped.
| Dependency | Supported Versions |
|---|---|
| Vuex | ^3.6.2 | ^4.0.2 |
| Node | ≥ 18 |
| TypeScript | ≥ 5.0 |
| Module format | ESM + CJS |
- Reentrant dispatches in the same namespace are recognized → no deadlock.
- Internal state is module-wide (fine for SPAs). For strict SSR isolation, move state into the factory.
See Releases for history and changes.
Maintained by IITS Consulting · © 2025 ·