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:
- Return
undefined without creating an entry, or
- 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
Description
When using Legend State v3 (
syncedSupabasewith record-type observables), accessing an observable with anundefinedornullkey silently creates a phantom entry in the observable's data.Reproduction
Expected behavior
Accessing
obs$[undefined]orobs$[null]on a record-type observable should either:undefinedwithout creating an entry, or__DEV__modeActual 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:.get()results (extra entries inObject.values())"undefined")Current workaround
We manually clean ghost entries after sync operations:
How it happens in practice
This is easy to trigger accidentally in real apps:
Environment
@legendapp/state:3.0.0-beta.46syncedSupabase