Skip to content

Array Handling in Legend-State: Add ability for shallow and deep compare of arrays#564

Open
mkuczera wants to merge 1 commit intoLegendApp:mainfrom
mkuczera:main
Open

Array Handling in Legend-State: Add ability for shallow and deep compare of arrays#564
mkuczera wants to merge 1 commit intoLegendApp:mainfrom
mkuczera:main

Conversation

@mkuczera
Copy link

@mkuczera mkuczera commented Aug 5, 2025

Problem

We stumbled upon a case where the observable was not reflecting the synced changes. In the specific case a query was not properly updating beside having received the correct payload from the network request. Our response was containing an array of items. On removal of an item the deepMerge was performing updates on the rest of the elements and not removing the intended item.

Proposal

When working with arrays in reactive applications, the way arrays are merged significantly affects performance and behavior. This proposal makes the handling of arrays configurable and prevents common issues like unwanted index-based merging while optimizing for performance. If this implementation is fine i can add the corresponding documentation also.

Configuration Options

arrayHandling Options

Option Behavior Performance Use Cases
'never' Legacy index-based merging ⚡ Fast Default - Backward compatibility
'shallow' Replace arrays when content differs (=== comparison) ⚡ Fast Recommended - Most use cases, primitive arrays, simple objects
'deep' Replace arrays when content differs (recursive comparison) 🐌 Slower Complex nested structures

Usage Examples

1. Sync Operations

TanStack Query

import { syncedQuery } from '@legendapp/state/sync-plugins/tanstack-query';

const users$ = observable(
  syncedQuery({
    queryClient,
    query: {
      queryKey: ['users'],
      queryFn: () => fetchUsers(),
    },
    // Configure array handling for query updates
    deepMerge: { arrayHandling: 'shallow' },
  })
);

// When TanStack Query updates:
// OLD: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
// NEW: [{ id: 1, name: 'Alice' }, { id: 3, name: 'Charlie' }]  
// Result: Complete array replacement ✅

Other Sync Plugins

// Firebase/Supabase
const data$ = observable(
  syncedSupabase({
    supabase,
    collection: 'users',
    select: (from) => from.select('*'),
    deepMerge: { arrayHandling: 'shallow' },
  })
);

// Custom sync
const items$ = observable(
  synced({
    get: () => fetchItems(),
    set: ({ value }) => saveItems(value),
    deepMerge: { arrayHandling: 'deep' },
  })
);

1. Global Configuration

Set default behavior for all sync operations:

import { configureObservableSync } from '@legendapp/state/sync';

// Configure globally
configureObservableSync({
  deepMerge: { arrayHandling: 'shallow' },
});

// Now all sync operations use shallow array handling by default
const users$ = observable(
  syncedQuery({
    queryClient,
    query: { queryKey: ['users'], queryFn: fetchUsers },
    // No need to specify deepMerge - uses global setting
  })
);

Common Patterns

API Data Updates

// User list from API
const users$ = observable(
  syncedQuery({
    queryClient,
    query: { queryKey: ['users'], queryFn: fetchUsers },
    deepMerge: { arrayHandling: 'shallow' }, // Replace user arrays
  })
);

// Real-time updates
const messages$ = observable(
  syncedSupabase({
    supabase,
    collection: 'messages',
    realtime: true,
    deepMerge: { arrayHandling: 'shallow' }, // Replace message arrays
  })
);

Complex Nested Data

// When you have arrays containing objects with nested arrays
const project$ = observable(
  syncedQuery({
    queryClient,
    query: { queryKey: ['project'], queryFn: fetchProject },
    deepMerge: { arrayHandling: 'deep' }, // Deep comparison for complex nesting
  })
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant