Vue.js 27 🟢 Debugging Lifecycle Hooks (onRenderTracked, onRenderTriggered)
Vue’s reactivity system is automatic, and most of the time that is exactly what you want. You change a ref, and the component re-renders. You read a ref in a template, and Vue records that the component depends on it. But when a component re-renders too often, or not often enough, the automatic behavior becomes opaque. Which reactive property triggered the render? Which properties is the component actually tracking? Why does this component re-render when that unrelated state changes? The answers are not visible in the component’s source code. They are in the dependency graph that Vue builds at runtime.
Vue 3 provides two debugging hooks that expose this graph: onRenderTracked and onRenderTriggered. They are lifecycle hooks, but they do not run at a fixed phase of the component’s life. They run during the reactivity system’s internal operations. onRenderTracked runs whenever the component’s render function reads a reactive property for the first time, recording a new dependency. onRenderTriggered runs whenever a reactive property that the component depends on changes, triggering a re-render. Together, they show the full picture: what the component tracks, and what causes it to update.
The hooks are development-only tools. They are intended for debugging, not for production logic. They fire frequently — onRenderTracked on the first read of each dependency, onRenderTriggered on every change to a tracked dependency. Using them for anything other than debugging would add overhead and complexity to the reactive system. The Vue documentation explicitly marks them as “for debugging” .
This chapter covers three areas. First, why the debugging hooks exist — the opacity of the automatic dependency graph, and the specific problems they solve: unexpected re-renders, missing reactivity, and performance issues. Second, how onRenderTracked and onRenderTriggered work — the DebuggerEvent object, the target, key, type, and effect properties, and the difference between a tracked read and a triggered write. Third, how to use them to diagnose real problems — the patterns for finding unnecessary dependencies, tracing the source of a re-render, and identifying the difference between ref unwrapping in templates and in script. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the dependency tracking flow.
Key point: onRenderTracked runs when the component’s render function reads a reactive property, recording a dependency. onRenderTriggered runs when a tracked reactive property changes, scheduling a re-render. Both receive a DebuggerEvent with target, key, type, and effect. Both are development-only debugging tools.
Why the debugging hooks exist
The automatic-dependency problem. Vue’s reactivity is not declared. A developer does not write “this component depends on count and name.” Instead, the component’s render function reads count and name, and Vue records the dependency automatically. This is convenient, but it makes the dependency graph invisible. There is no declaration to read, no list of dependencies to inspect. When a component re-renders unexpectedly, the cause is not in the source code; it is in the runtime dependency graph. The onRenderTracked hook exposes that graph by reporting each dependency as it is recorded .
The unexpected-re-render problem. A component re-renders when a tracked dependency changes. If a component depends on more properties than it appears to, it re-renders more often than expected. This is the most common performance issue in Vue applications. A useFetch composable that stores loading, error, and data in a single reactive object, and returns the whole object to the component, causes the component to re-render whenever any of the three changes. The component may only use data in its template, but it depends on all three because the object is read as a whole. onRenderTracked reveals this: it reports the dependency on the object, including the properties that the template does not use. The fix is often to destructure the object or to return individual refs .
The missing-reactivity problem. A component does not re-render when a dependency changes if the dependency was not tracked. This happens when a reactive property is read outside the render function — in an event handler, in a watch callback, or in a computed that is not itself used in the render. The property is not tracked by the render, so changes to it do not trigger a re-render. onRenderTriggered shows the inverse: if a change to a property does not produce a trigger event, the render does not depend on it. The fix is to move the read into the render function, or to introduce a computed that the render uses .
The performance problem. onRenderTriggered fires on every change to a tracked dependency. In a component with many dependencies and frequent updates, the hook fires often. The DebuggerEvent includes an effect property that identifies the render effect. By examining the event, a developer can see which property changed, and whether the change is expected. A component that re-renders on every keystroke because it tracks a text input’s value is a common case: the input is bound with v-model, and the component re-renders on every character. The hook shows the trigger source, and the fix is often to debounce the input or to isolate the input into a child component .
The ref-unwrapping problem. In a template, a ref is automatically unwrapped, so count reads count.value. In script, count is the ref object, and count.value is the value. The dependency is tracked on the .value access. onRenderTracked reports the target as the ref object and the key as 'value'. This distinction is important: a developer who expects the dependency to be on the component’s setup state may be confused by the ref object in the event. The key: 'value' indicates that the dependency is on the ref’s value, not on a property of the ref.
The trade-off. The debugging hooks add observability at a cost. They fire frequently, and the callbacks run synchronously during the reactivity system’s operations. Using them in production would add overhead and could change the timing of the component’s updates. They are intended for development, and they should be removed or disabled in production builds. The trade-off is between the visibility they provide during development and the overhead they add at runtime.
a. onRenderTracked — recording dependencies
onRenderTracked registers a callback that runs whenever the component’s render effect tracks a reactive dependency. The callback receives a DebuggerEvent with the details of the tracked read. The hook runs during the render, so its output is associated with the component that is rendering .
<script setup>
import { ref, onRenderTracked } from 'vue';
const count = ref(0);
const name = ref('Alice');
onRenderTracked((event) => {
console.log('tracked:', event);
});
</script>
<template>
<p>{{ count }}</p>
</template>
In this example, the template reads count but not name. The render effect tracks count when it reads it, and onRenderTracked fires once with the count ref. It does not fire for name, because name is not read in the render. The event’s target is the count ref object, and the key is 'value'.
The DebuggerEvent for a tracked read has these properties:
target: The reactive object or ref that was read.key: The property that was read. For a ref, this is'value'.type: The type of operation. For tracking, this is'get'.effect: The reactive effect that performed the read. For a component render, this is the render effect .
The hook can be used to build a list of all dependencies that a component tracks. The list is complete after the first render: every reactive property that the render reads is reported once. Subsequent renders do not re-report dependencies that are already tracked, unless the render reads a new property that it did not read before.
b. onRenderTriggered — reporting re-renders
onRenderTriggered registers a callback that runs whenever a tracked dependency changes, causing the component to re-render. The callback receives a DebuggerEvent with the details of the change. The hook runs before the re-render, so the callback can inspect the state that triggered the update .
<script setup>
import { ref, onRenderTriggered } from 'vue';
const count = ref(0);
const name = ref('Alice');
onRenderTriggered((event) => {
console.log('triggered by:', event);
});
function increment() {
count.value++;
}
function changeName() {
name.value = 'Bob';
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
<button @click="changeName">Change Name</button>
</template>
The template reads count, so the component tracks count. When increment() changes count, onRenderTriggered fires with the count ref. When changeName() changes name, the component does not track name, so the change does not trigger a re-render, and onRenderTriggered does not fire. This is the core diagnostic: if a change does not produce a trigger event, the render does not depend on the changed property.
The DebuggerEvent for a trigger has these properties:
target: The reactive object or ref that changed.key: The property that changed. For a ref, this is'value'.type: The type of operation. For arefassignment, this is'set'. For an array mutation, this is'add'or'delete'. For aMaporSet, this is'add','delete', or'clear'.newValue: The new value.oldValue: The previous value.effect: The reactive effect that was triggered .
The type field distinguishes between different kinds of changes. A 'set' operation is a direct assignment. An 'add' operation is a new property on an object or a new element in an array. A 'delete' operation is a property removal. The newValue and oldValue fields show the before and after values, which is useful for confirming that the change is what the developer expects.
c. Using the hooks to diagnose problems
The debugging hooks are most useful when combined with a specific question. The question determines which hook to use and what to look for.
Finding unnecessary dependencies. Use onRenderTracked and log the target and key of each event. The list of tracked dependencies is the component’s actual dependency set. Compare it to the properties the component appears to use. If the list includes properties that the template does not reference, the component is tracking more than it needs, and it will re-render more often than necessary. The fix is to narrow the dependency: destructure the reactive object, use toRefs, or split the state into separate refs.
Tracing the source of a re-render. Use onRenderTriggered and log the target, key, newValue, and oldValue of each event. The event identifies which property changed and to what value. If the component re-renders unexpectedly, the event shows the cause. If the event fires for a property that the component does not appear to use, the dependency was tracked indirectly — often through an object that the component reads as a whole.
Confirming missing reactivity. If a change to a property does not produce a trigger event, the render does not depend on that property. This is the diagnosis for missing reactivity: the property is read outside the render, so it is not tracked. The fix is to move the read into the render, or to introduce a computed that the render uses.
Identifying template ref unwrapping. The target of a tracked or triggered event is the ref object, and the key is 'value'. This confirms that the dependency is on the ref’s value. If a developer expects the dependency to be on a property of the ref object itself, the key: 'value' clarifies that it is not.
Counting re-renders. A counter incremented in onRenderTriggered shows how many times a component re-renders. If the count is higher than expected, the events identify the causes. This is a quick way to quantify a performance problem before investigating the fix.
Complete Example Session
<!-- ============================================
PART 1: A BASIC onRenderTracked HOOK
============================================ -->
<script setup>
import { ref, onRenderTracked } from 'vue';
const count = ref(0);
onRenderTracked((event) => {
console.log('tracked:', event.key, 'on', event.target);
});
</script>
<template>
<p>{{ count }}</p>
</template>
<!-- Output on first render:
tracked: value on RefImpl { ... }
The component tracks count.value. -->
<!-- ============================================
PART 2: TRACKING MULTIPLE DEPENDENCIES
============================================ -->
<script setup>
import { ref, onRenderTracked } from 'vue';
const count = ref(0);
const name = ref('Alice');
const unused = ref('not in template');
onRenderTracked((event) => {
console.log('tracked:', event.key);
});
</script>
<template>
<p>{{ count }}</p>
<p>{{ name }}</p>
</template>
<!-- Output:
tracked: value (for count)
tracked: value (for name)
'unused' is NOT tracked because it is not in the template. -->
<!-- ============================================
PART 3: A BASIC onRenderTriggered HOOK
============================================ -->
<script setup>
import { ref, onRenderTriggered } from 'vue';
const count = ref(0);
onRenderTriggered((event) => {
console.log('triggered:', {
key: event.key,
newValue: event.newValue,
oldValue: event.oldValue,
type: event.type,
});
});
function increment() {
count.value++;
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
</template>
<!-- Output on click:
triggered: { key: 'value', newValue: 1, oldValue: 0, type: 'set' } -->
<!-- ============================================
PART 4: UNTRACKED PROPERTIES DO NOT TRIGGER
============================================ -->
<script setup>
import { ref, onRenderTriggered } from 'vue';
const count = ref(0);
const name = ref('Alice');
onRenderTriggered((event) => {
console.log('triggered by:', event.key);
});
function changeName() {
name.value = 'Bob'; // name is not in the template
}
</script>
<template>
<p>{{ count }}</p>
<button @click="changeName">Change Name</button>
</template>
<!-- Clicking the button does NOT produce a trigger event,
because the template does not read name. -->
<!-- ============================================
PART 5: THE UNNECESSARY DEPENDENCY PROBLEM
============================================ -->
<script setup>
import { reactive, onRenderTracked } from 'vue';
const state = reactive({
data: null,
loading: false,
error: null,
});
onRenderTracked((event) => {
console.log('tracked:', event.key);
});
async function fetchData() {
state.loading = true;
state.data = await fetch('/api').then((r) => r.json());
state.loading = false;
}
</script>
<template>
<p v-if="state.loading">Loading...</p>
<p v-else>{{ state.data }}</p>
</template>
<!-- The template reads state.loading and state.data.
But because the template accesses state as an object,
it may also track other properties. -->
<!-- ============================================
PART 6: NARROWING THE DEPENDENCY
============================================ -->
<script setup>
import { reactive, toRefs, onRenderTracked } from 'vue';
const state = reactive({
data: null,
loading: false,
error: null,
});
// Destructure to individual refs to narrow tracking
const { data, loading, error } = toRefs(state);
onRenderTracked((event) => {
console.log('tracked:', event.key);
});
</script>
<template>
<p v-if="loading">Loading...</p>
<p v-else>{{ data }}</p>
</template>
<!-- With toRefs, the template tracks only loading and data,
not the entire state object. -->
<!-- ============================================
PART 7: LOGGING THE FULL EVENT
============================================ -->
<script setup>
import { ref, onRenderTriggered } from 'vue';
const count = ref(0);
const items = ref([]);
onRenderTriggered((event) => {
console.log('type:', event.type);
console.log('target:', event.target);
console.log('key:', event.key);
console.log('newValue:', event.newValue);
console.log('oldValue:', event.oldValue);
});
function addItem() {
items.value.push({ id: Date.now() });
}
</script>
<template>
<p>{{ count }}</p>
<p>{{ items.length }}</p>
<button @click="addItem">Add</button>
</template>
<!-- Pushing to items produces a trigger event with:
type: 'add'
key: '0' (or the new index)
newValue: { id: ... } -->
<!-- ============================================
PART 8: COUNTING RE-RENDERS
============================================ -->
<script setup>
import { ref, onRenderTriggered } from 'vue';
const count = ref(0);
let renderCount = 0;
onRenderTriggered(() => {
renderCount++;
console.log(`Re-render #${renderCount}`);
});
</script>
<template>
<p>{{ count }}</p>
<button @click="count++">Increment</button>
</template>
<!-- Each click logs a new re-render count. -->
<!-- ============================================
PART 9: COMBINING BOTH HOOKS
============================================ -->
<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue';
const count = ref(0);
const name = ref('Alice');
onRenderTracked((event) => {
console.log('[TRACK]', event.key, 'on', event.target);
});
onRenderTriggered((event) => {
console.log('[TRIGGER]', event.key, 'from', event.oldValue, 'to', event.newValue);
});
function increment() {
count.value++;
}
function changeName() {
name.value = 'Bob';
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">+</button>
<button @click="changeName">Name</button>
</template>
<!-- First render:
[TRACK] value on RefImpl
[TRACK] value on RefImpl
(one for count, one for... wait, name is not in template)
Clicking "+":
[TRIGGER] value from 0 to 1
Clicking "Name":
(no event, because name is not tracked) -->
<!-- ============================================
PART 10: THE COMPLETE DEBUGGING COMPONENT
============================================ -->
<script setup>
import { ref, computed, onRenderTracked, onRenderTriggered } from 'vue';
const firstName = ref('Alice');
const lastName = ref('Smith');
const age = ref(30);
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
onRenderTracked((event) => {
console.debug('[track]', {
key: event.key,
type: event.type,
});
});
onRenderTriggered((event) => {
console.debug('[trigger]', {
key: event.key,
type: event.type,
oldValue: event.oldValue,
newValue: event.newValue,
});
});
</script>
<template>
<p>{{ fullName }}</p>
<p>{{ age }}</p>
<button @click="age++">Birthday</button>
<button @click="firstName = 'Bob'">Change First Name</button>
</template>
<!-- The template tracks fullName (a computed) and age.
fullName depends on firstName and lastName internally,
but the component's render only tracks the computed.
Changing firstName triggers the computed to re-evaluate,
which triggers the render. The trigger event shows the
computed as the target. -->
The ten parts show a basic onRenderTracked hook, tracking multiple dependencies, a basic onRenderTriggered hook, untracked properties not triggering, the unnecessary dependency problem, narrowing the dependency with toRefs, logging the full event, counting re-renders, combining both hooks, and the complete debugging component.
Quick Reference
Debugging Hooks
| Hook | Runs when | Receives |
|---|---|---|
onRenderTracked | Render reads a reactive property | DebuggerEvent with type: 'get' |
onRenderTriggered | Tracked property changes | DebuggerEvent with type: 'set'/'add'/'delete' |
DebuggerEvent Properties
| Property | Meaning |
|---|---|
target | The reactive object or ref |
key | The property that was read or changed |
type | 'get', 'set', 'add', 'delete', 'clear' |
newValue | New value (trigger only) |
oldValue | Old value (trigger only) |
effect | The reactive effect |
Tracked vs Triggered
| Aspect | Tracked | Triggered |
|---|---|---|
| When | On read | On write |
| Type | 'get' | 'set', 'add', 'delete' |
| Frequency | Once per dependency | Every change |
| Use | Find dependencies | Find re-render causes |
Common Diagnoses
| Symptom | Hook | What to look for |
|---|---|---|
| Too many re-renders | onRenderTriggered | Unexpected key or target |
| Re-render not happening | onRenderTriggered | No event when property changes |
| Unnecessary dependencies | onRenderTracked | target for unused properties |
| Ref unwrapping | Both | key: 'value' on a RefImpl |
Best Practices
✅ Do This:
<script setup>
// Use onRenderTracked to find dependencies
onRenderTracked((event) => console.log(event.key)); // ✅
// Use onRenderTriggered to find re-render causes
onRenderTriggered((event) => console.log(event.key, event.newValue)); // ✅
// Log the full event during investigation
console.log({ target: event.target, key: event.key, type: event.type }); // ✅
// Remove the hooks after debugging
// (do not ship them to production) // ✅
// Use toRefs to narrow dependencies
const { data, loading } = toRefs(state); // ✅
</script>
❌ Don’t Do This:
<script setup>
// Don't ship debugging hooks to production
onRenderTracked((event) => { /* ... */ }); // dev only // ❌
// Don't mutate state inside the hooks
onRenderTriggered(() => { count.value++; }); // infinite loop // ❌
// Don't use the hooks for business logic
onRenderTracked((event) => { logToServer(event); }); // ❌
// Don't assume the target is the component
// It is the reactive object or ref // ❌
// Don't expect onRenderTracked to fire on every render
// It fires only when a new dependency is tracked // ❌
</script>
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Hook fires too often | Every change to a tracked dependency | Expected; use for debugging only |
| Hook does not fire | Property not tracked | Check that the render reads it |
target is a RefImpl | Ref unwrapping | The key is 'value' |
| Infinite loop | State mutation in the hook | Never mutate state in debugging hooks |
| Production overhead | Hooks shipped to production | Remove or guard with import.meta.env.DEV |
| Confusing tracked and triggered | Read vs write | Tracked = read; triggered = write |
Real-World Examples
1. Log Tracked Dependencies
onRenderTracked((event) => console.log(event.key));
2. Log Trigger Source
onRenderTriggered((event) => console.log(event.key, event.newValue));
3. Count Re-renders
let count = 0;
onRenderTriggered(() => console.log(++count));
4. Log Full Event
onRenderTriggered((event) => console.log({
target: event.target, key: event.key, type: event.type,
}));
5. Detect Untracked Change
// Click a button that changes a non-template ref
// No trigger event fires
6. Detect Unnecessary Dependency
// onRenderTracked shows a property not used in template
7. Use toRefs
const { data, loading } = toRefs(state);
8. Guard for Dev
if (import.meta.env.DEV) {
onRenderTracked((event) => console.log(event));
}
9. Debug a Computed
onRenderTriggered((event) => console.log(event.target));
10. Compare Before and After
onRenderTriggered((event) => console.log(event.oldValue, '->', event.newValue));
Visual
The Dependency Tracking Flow
┌──────────────────────────────────────────────────────────────┐
│ DEPENDENCY TRACKING FLOW │
│ │
│ Component renders │
│ │ │
│ ▼ │
│ Render function executes │
│ │ │
│ ├── reads count.value │
│ │ → track: target=count, key='value' │
│ │ → onRenderTracked fires │
│ │ │
│ ├── reads name.value │
│ │ → track: target=name, key='value' │
│ │ → onRenderTracked fires │
│ │ │
│ └── renders template │
│ │ │
│ ▼ │
│ Dependency set: { count, name } │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ On state change: │ │
│ │ │ │
│ │ count.value = 1 │ │
│ │ → count is in the dependency set │ │
│ │ → onRenderTriggered fires │ │
│ │ → component re-renders │ │
│ │ │ │
│ │ unused.value = 1 │ │
│ │ → unused is NOT in the dependency set │ │
│ │ → no trigger, no re-render │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
DebuggerEvent Structure
┌──────────────────────────────────────────────────────────────┐
│ DEBUGGEREVENT STRUCTURE │
│ │
│ Tracked event (type: 'get'): │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ target: RefImpl, ← the ref object │ │
│ │ key: 'value', ← the property read │ │
│ │ type: 'get', ← read operation │ │
│ │ effect: ReactiveEffect ← the render effect │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Triggered event (type: 'set'): │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ target: RefImpl, ← the ref object │ │
│ │ key: 'value', ← the property changed │ │
│ │ type: 'set', ← write operation │ │
│ │ newValue: 1, ← new value │ │
│ │ oldValue: 0, ← previous value │ │
│ │ effect: ReactiveEffect ← the render effect │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Unnecessary Dependency Problem
┌──────────────────────────────────────────────────────────────┐
│ UNNECESSARY DEPENDENCY │
│ │
│ const state = reactive({ data, loading, error }); │
│ │
│ <template> │
│ <p v-if="state.loading">Loading...</p> │
│ <p v-else>{{ state.data }}</p> │
│ </template> │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The template reads: │ │
│ │ state.loading │ │
│ │ state.data │ │
│ │ │ │
│ │ The component tracks: │ │
│ │ state (the whole object) │ │
│ │ │ │
│ │ When state.error changes, the component re-renders, │ │
│ │ even though the template does not use error. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ FIX: destructure with toRefs │
│ const { data, loading } = toRefs(state); │
│ Now the template tracks only data and loading. │
│ │
└──────────────────────────────────────────────────────────────┘
Detecting Missing Reactivity
┌──────────────────────────────────────────────────────────────┐
│ MISSING REACTIVITY │
│ │
│ const count = ref(0); │
│ │
│ onMounted(() => { │
│ console.log(count.value); // read outside render │
│ }); │
│ │
│ <template> │
│ <p>Static</p> ← does NOT read count │
│ </template> │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ count.value = 1 │ │
│ │ → count is NOT tracked by the render │ │
│ │ → onRenderTriggered does NOT fire │ │
│ │ → component does NOT re-render │ │
│ │ │ │
│ │ DIAGNOSIS: the read is outside the render. │ │
│ │ │ │
│ │ FIX: move the read into the template, or add a │ │
│ │ computed that the template uses. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
onRenderTracked | Fires when the render reads a reactive property |
onRenderTriggered | Fires when a tracked property changes |
DebuggerEvent | target, key, type, newValue, oldValue, effect |
| Tracked type | 'get' |
| Triggered types | 'set', 'add', 'delete', 'clear' |
| Ref unwrapping | target is the ref; key is 'value' |
| Production | Development-only; remove or guard |
| Common use | Find unnecessary deps, trace re-renders, confirm reactivity |
Key takeaways:
- The debugging hooks expose the dependency graph.
onRenderTrackedreports each dependency as the render reads it.onRenderTriggeredreports each change that causes a re-render. Together, they show what the component tracks and what triggers it . onRenderTrackedfires once per dependency. It does not fire on every render; it fires when a new dependency is tracked. After the first render, the dependency set is complete, and the hook does not fire again unless the render reads a new property.onRenderTriggeredfires on every change to a tracked dependency. It is the hook for tracing re-renders. If a change does not produce a trigger event, the render does not depend on the changed property.- The
DebuggerEventidentifies the source. Thetargetis the reactive object or ref, thekeyis the property, and thetypeis the operation. For a ref, thekeyis'value', and thetargetis the ref object. - The hooks diagnose unnecessary dependencies. A component that reads a reactive object as a whole tracks all of its properties, even the ones the template does not use.
onRenderTrackedreveals this, and the fix is to narrow the dependency withtoRefsor separate refs . - The hooks diagnose missing reactivity. A property that is read outside the render is not tracked.
onRenderTriggereddoes not fire when it changes, which confirms that the render does not depend on it. The fix is to move the read into the render . - The hooks are development-only. They fire frequently and add overhead. They should be removed or guarded with
import.meta.env.DEVbefore production. They are not a substitute forwatchorcomputed; they are a diagnostic tool . - Never mutate state inside the hooks. A mutation in
onRenderTriggeredtriggers another re-render, which fires the hook again, which mutates the state again. This is an infinite loop. The hooks are for observation, not for logic .
Remember: Vue’s reactivity is automatic, and that automatic behavior is usually what you want. But when a component re-renders too often or not often enough, the cause is in the dependency graph, not in the source code. onRenderTracked and onRenderTriggered expose that graph. The first shows what the component tracks; the second shows what triggers it. The DebuggerEvent identifies the exact property and the exact change. With these hooks, the questions “why did this re-render?” and “why didn’t this re-render?” have concrete answers. The hooks are development-only, they fire frequently, and they should never mutate state. Use them to diagnose, not to build.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!