Meituan-Dianping/mpvue · error
You may have an infinite update loop in watcher with express
Error message
You may have an infinite update loop in watcher with expression "${watcher.expression}" / in a component render function. What it means
Dev-mode warning from the scheduler's flushSchedulerQueue when the same watcher is re-queued and run more than MAX_UPDATE_COUNT (100) times in one flush cycle. It indicates a reactive update loop: a watcher or render function mutates state that triggers itself again.
Source
Thrown at src/core/observer/scheduler.js:63
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
View on GitHub (pinned to 6c5d78ee04)
Solutions
- Find the watcher expression named in the warning and remove its self-triggering write
- Move mutations out of render/computed getters into methods or lifecycle hooks
- Guard the watcher: only assign when the value actually changed
- Break parent-child cycles by emitting events upward instead of writing props back
- Use a debounce/nextTick for cascading updates
Example fix
// before
watch: { value(v) { this.value = v.trim() } } // re-triggers itself
// after
computed: {
trimmed: {
get() { return this.value },
set(v) { this.$emit('input', v.trim()) }
}
} Defensive patterns
Strategy: validation
Validate before calling
function watcherWritesWatchedKey(watchers, computedKeys) {
const offenders = []
for (const [expr, def] of Object.entries(watchers || {})) {
const handler = typeof def === 'function' ? def : def && def.handler
if (handler && String(handler).includes('this.' + expr)) offenders.push(expr)
}
return offenders
} Type guard
function isAcyclicWatcher(expr, handler) { return !String(handler).includes('this.' + expr) } Prevention
- Never mutate the watched value inside its own handler
- Keep computed getters pure; do side effects in methods/watchers
- Avoid parent-child prop/event write cycles; emit upward only
- Add value-change guards in watchers before assigning
When it happens
Trigger: A watcher whose handler sets the same value it watches; a computed that both reads and writes state consumed by the render; a component render function mutating reactive data used in the template (e.g. calling a method that increments during render); two watchers updating each other in a cycle.
Common situations: v-model on a computed without a setter guard; parent-child prop/event ping-pong where each side writes on change; watchers with deep:true on large mutating structures; side effects in computed getters.
Related errors
- Failed watching path: "${expOrFn}" Watcher only accepts simp
- Avoid mutating a prop directly since the value will be overw
- Avoid replacing instance root $data. Use nested data propert
- $props is readonly.
- Avoid mutating an injected value directly since the changes
AI-assisted analysis of Meituan-Dianping/mpvue@6c5d78ee04 (2026-09-02).
Data as JSON: /api/errors/f68931d2c5805809.
Report an issue: GitHub.