Skip to content

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

@zakhar-gulchak

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions