#15632·Vue.js

Component update is skipped when object v-bind changes keys and the new prop value is undefined

Author: verokhovCreated Sep 24, 2026Updated Sep 29, 2026
Labels:hammer: p3-minor-bug

Vue version

3.5.38

Link to minimal reproduction

https://play.vuejs.org/#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

Steps to reproduce

  1. Open the reproduction.
  2. Notice that both child components initially display "Add at least one item".
  3. Click "Add item".
  4. The click handler adds an item and schedules the counter update using nextTick().
  5. Wait for the updates to complete.
  6. Observe that items contains one item, hasErrors is false, and counter is 1.
  7. Compare the results of the "Dynamic object shape" and "Stable object shape" components.

What is expected?

Both child components should clear the previous errorMessage prop and display:

Count: 1

When the object passed through v-bind changes from:

javascript
{
  errorMessage: 'Add at least one item',
}

to:

javascript
{
  counter: undefined,
}

the removal of errorMessage should be detected even though the new prop value is undefined.

The watcher for props.errorMessage should be triggered with the previous error message and undefined as the new value.

What is actually happening?

The component using the stable-shaped binding works correctly: it clears errorMessage and displays:

Count: 1

The component using the dynamically shaped binding receives the updated counter but retains the stale errorMessage.

Its effective props become:

javascript
{
  errorMessage: 'Add at least one item',
  counter: 1,
}

As a result, it continues to display:

Add at least one item

The computed binding in the parent is recalculated correctly, but the watcher for props.errorMessage in the child is not triggered because the stale prop is never cleared.

System Info

bash
Reproduces in the Vue SFC Playground using Chrome.

Any additional comments?

The reproduction uses nextTick() to intentionally split the state change into two render cycles:

javascript
const addItem = () => {
  items.value.push('Item');

  nextTick(() => counter.value = items.value.length);
};

This produces the following props transitions:

javascript
// Initial render
{
  errorMessage: 'Add at least one item',
}

// First render after adding an item
{
  counter: undefined,
}

// Second render after nextTick()
{
  counter: 1,
}

During the first update, the previous and next props objects contain the same number of keys:

javascript
const prevProps = {
  errorMessage: 'Add at least one item',
};

const nextProps = {
  counter: undefined,
};

Object.keys(prevProps).length === Object.keys(nextProps).length;
// true

When Vue checks the counter key from nextProps, the comparison is effectively:

javascript
nextProps.counter === prevProps.counter;
// undefined === undefined
// true

counter is not present in prevProps, but reading a missing property also returns undefined. Because the comparison checks the values without checking property presence, Vue considers the props unchanged and skips the child component update.

The child component therefore retains:

javascript
{
  errorMessage: 'Add at least one item',
}

However, after the skipped update, the component VNode has already advanced to:

javascript
{
  counter: undefined,
}

When counter is changed to 1 after nextTick(), Vue detects that update and patches the child. At this point, the previous VNode no longer contains errorMessage, so the stale errorMessage stored in the component instance is not cleared.

The child consequently ends up with both the stale error and the new counter:

javascript
{
  errorMessage: 'Add at least one item',
  counter: 1,
}

The stable-shaped version avoids the issue because Vue can directly detect the errorMessage value change:

javascript
const stableBind = computed(() => ({
  errorMessage: hasErrors.value
    ? 'Add at least one item'
    : undefined,
  counter: counter.value,
}));

A possible fix may be to account for property presence when comparing props, in addition to comparing their values:

javascript
!hasOwn(prevProps, key) ||
hasPropValueChanged(nextProps, prevProps, key)

Object-style v-bind is documented as equivalent to passing the object properties individually. The documentation does not state that the bound object must retain the same set of keys: https://vuejs.org/guide/components/props.html#binding-multiple-properties-using-an-object