Skip to main content

Mutations Class

The Mutations class handles all cache mutations and optimistic updates. It provides methods to add, remove, and update items in the cached data while maintaining consistency across all related queries.

When to use Mutations

Use Mutations when you want to deliberately edit cached resource data.

Good fits:

  • optimistic create, update, and delete flows
  • immediate UI updates after action-style mutations
  • cache sync between list and detail views
  • restoring removed items after rollback

If the job is only to invalidate, refetch, cancel, prefetch, or inspect queries, use QueryKeys instead.

Mutations vs invalidation

Do not assume invalidation is always the best answer.

Prefer direct cache mutation when:

  • you already know the next resource state
  • the UI should update immediately
  • the manager already exposes the right mutation helper

Prefer invalidation when:

  • the server response is easier to trust than a local patch
  • many derived queries may need recomputation
  • the affected cached shape is too broad or uncertain to patch confidently

Adding Items

The addItem method adds new items to cached list data with flexible positioning:

// Access Mutations from QueryManager instance  
const { mutations } = userQueryManager

// Add to start of list (default)
mutations.addItem(newUser, 'start')

// Add to end of list
mutations.addItem(newUser, 'end')

// Add to specific list with filters
mutations.addItem(newUser, 'start', { status: 'active' })

// Restore item to original positions (from removed item map)
const removedPositions = mutations.removeItem('user-123')
if (removedPositions) {
mutations.addItem(restoredUser, removedPositions)
}

Position Restoration

When items are removed, the Mutations class tracks their exact positions across all list queries:

interface ItemPosition {
pageIndex: number
itemIndex: number
}

type RemovedItemMap = Array<[QueryKey, ItemPosition]>

// Example: User was at position 2 in page 0 of active users list
// and position 5 in page 1 of all users list
const removedPositions: RemovedItemMap = [
[['users', 'list', { status: 'active' }], { pageIndex: 0, itemIndex: 2 }],
[['users', 'list'], { pageIndex: 1, itemIndex: 5 }]
]

Removing Items

The removeItem method removes items from all cached list queries and returns position information for potential restoration:

// Remove item and get positions for rollback
const removedPositions = mutations.removeItem('user-123')

if (removedPositions) {
console.log('Item was found in', removedPositions.length, 'queries')

// Later, restore to original positions
mutations.addItem(user, removedPositions)
}

// If item wasn't found in any cache
if (!removedPositions) {
console.log('Item not found in any cached list')
}

Updating Items

The updateItems method handles both single item and batch updates with smart change detection:

// Update single item
mutations.updateItems({
id: 'user-123',
name: 'Updated Name',
status: 'inactive'
})

// Update multiple items
mutations.updateItems([
{ id: 'user-123', name: 'John Updated' },
{ id: 'user-456', status: 'active' },
{ id: 'user-789', role: 'admin' }
])

// Update with temporary ID (during optimistic creates)
mutations.updateItems({
id: 'user-123',
tempId: 'temp-abc123',
name: 'Real User Data'
})

Smart Change Detection

The Mutations class uses deep equality checking to avoid unnecessary updates:

// Only updates cache if data actually changed
const existingUser = { id: 'user-123', name: 'John', status: 'active' }
const updateData = { id: 'user-123', name: 'John', status: 'active' }

mutations.updateItems(updateData) // No cache update - data is identical

const changedData = { id: 'user-123', name: 'Jane', status: 'active' }
mutations.updateItems(changedData) // Cache updated - name changed

Temporary ID Handling

During optimistic updates, the library uses temporary IDs that are later replaced:

// During optimistic create
const tempId = generateTempId() // generates unique temporary ID
const tempUser = { ...userData, id: tempId, tempId }

mutations.addItem(tempUser, 'start')

// When server responds with real data
const realUser = { ...serverResponse, tempId } // include tempId for matching
mutations.updateItems(realUser) // finds item by tempId, updates with real data