Skip to content

Proxy creates phantom entries when accessing undefined/null keys on record observables #646

Description

@zakhar-gulchak

Description

When using Legend State v3 (syncedSupabase with record-type observables), accessing an observable with an undefined or null key silently creates a phantom entry in the observable's data.

Reproduction

import { observable } from '@legendapp/state';
import { syncedSupabase } from '@legendapp/state/sync-plugins/supabase';

const cards$ = observable<Record<string, Card>>(
  syncedSupabase({ /* ... */ })
);

// This accidentally creates a phantom entry keyed "undefined":
const deckId: string | undefined = undefined;
const value = cards$[deckId].get();  // or .peek()

// Now cards$.get() contains { "undefined": { ... proxy stub } }
// Same happens with null:
const value2 = cards$[null as any].get();
// cards$.get() now also contains { "null": { ... } }

Expected behavior

Accessing obs$[undefined] or obs$[null] on a record-type observable should either:

  1. Return undefined without creating an entry, or
  2. Throw a warning in __DEV__ mode

Actual behavior

A phantom entry is created with key "undefined" or "null". When the observable is synced (e.g. with Supabase), these ghost entries can cause:

  • Incorrect .get() results (extra entries in Object.values())
  • Failed sync attempts (trying to persist records with id "undefined")
  • Subtle UI bugs (empty/broken items in lists)

Current workaround

We manually clean ghost entries after sync operations:

const cleanGhosts = (obs$) => {
  const data = obs$.get();
  if (!data) return 0;
  let cleaned = 0;
  for (const key of Object.keys(data)) {
    if (!key || key === 'undefined' || key === 'null' || !data[key]?.id) {
      obs$[key].delete();
      cleaned++;
    }
  }
  return cleaned;
};

How it happens in practice

This is easy to trigger accidentally in real apps:

// Common pattern — card.deck_id can be null
const deckName = decks$[card.deck_id].name.get();
// If deck_id is null/undefined → ghost entry created

Environment

  • @legendapp/state: 3.0.0-beta.46
  • Platform: React Native (Expo)
  • Sync plugin: syncedSupabase

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions