๐ŸŽฏ

watermelondb

๐ŸŽฏSkill

from jchaselubitz/drill-app

VibeIndex|
What it does

Enables reactive database interactions in React using WatermelonDB, managing model observations, queries, and ensuring proper state updates and re-renders.

watermelondb

Installation

YarnRun with Yarn
yarn start:expo
๐Ÿ“– Extracted from docs: jchaselubitz/drill-app
0
-
Last UpdatedJan 29, 2026

Skill Details

SKILL.md

WatermelonDB models, observation patterns, and React integration. Use when writing or debugging model code, observers (findAndObserve, query.observe), or screens that display live-updating DB data.

Overview

# WatermelonDB Model & Observation

Overview

This skill covers WatermelonDB models (database/models/), observation

(reactive queries), and **ensuring React re-renders when observed data

changes**. Use it when working with findAndObserve, query.observe(),

withObservables, or any screen that subscribes to DB changes.

---

Observation & React Re-rendering

`findAndObserve` and same-reference emission

  • findAndObserve(id) (on a collection): Fetches a record by ID, returns an

Observable that emits immediately on subscribe and **whenever the record

is updated or deleted**.

  • When a model is updated (e.g. via model.update() or @writer methods), the

observable emits the same object reference with updated properties โ€” it

does not emit a new model instance.

React `useState` bailout

  • useState uses Object.is to decide whether to re-render. Passing the **same

reference (e.g. setPhrase(model)) after an update means no re-render**.

  • Result: DB updates (text, note, language, etc.) donโ€™t appear until the

user navigates away and back, when a new subscription yields a fresh

reference.

Fix: store a wrapper so each emit is a new reference

When subscribing to a single model (e.g. findAndObserve) and storing it in

React state, donโ€™t store the raw model. Store a wrapper so every emission

updates state with a new object:

```ts

const [phraseState, setPhraseState] = useState<

{ phrase: Phrase; _key: number } | null

>(null);

useEffect(() => {

if (!id) return;

const sub = db.collections

.get(PHRASE_TABLE)

.findAndObserve(id)

.subscribe((result) => {

setPhraseState({ phrase: result, _key: result.updatedAt });

});

return () => sub.unsubscribe();

}, [id, db]);

const phrase = phraseState?.phrase ?? null;

```

  • Use phrase (derived) everywhere in the component. Updates persisted to the

DB will re-emit, update phraseState with a new wrapper, and trigger a

re-render.

Query `observe()` and arrays

  • query.observe() emits arrays of models. When the query result set

changes, WatermelonDB typically emits a new array reference, so

setState(results) usually triggers re-renders.

  • If you build derived data (e.g. linked.filter(...), assignments) in

the subscribe callback and setState that, youโ€™re already passing new

references โ€” no extra wrapper needed.

---

`withObservables` (HOC)

  • withObservables(triggerProps, getObservables) injects observable values

as props and always passes a new state object into setState (e.g.

{ values, isFetching }), so React re-renders on each emission even when

model references are unchanged.

  • Use it when you can observe a model (or query) passed as a prop: e.g.

withObservables(['attempt'], ({ attempt }) => ({ attempt: attempt.observe(), ... })).

See AttemptCard in features/lesson/components/AttemptCard.tsx.

  • For route params (e.g. id) you typically subscribe manually in

useEffect (e.g. findAndObserve(id)). In that case, use the **wrapper

pattern** above instead of storing the raw model.

---

Model patterns in this project

  • Models: database/models/ (e.g. Phrase, Lesson, Attempt,

Translation, Deck). Use @field, @writer, and static helpers (e.g.

Phrase.findOrCreatePhrase, Lesson.addLesson).

  • Observation: useDatabase() from @nozbe/watermelondb/react; then

collection.findAndObserve(id) or query.observe().subscribe(...).

  • Schema/tables: database/schema.ts; collection access via

db.collections.get(TABLE).

---

Quick reference

| Scenario | Pattern | Re-render guarantee |

| ----------------------------------------- | ------------------------------------------------------------------------- | ------------------------------ |

| Single model by id (e.g. detail screen) | findAndObserve + wrapper state { model, _key } | Yes |

| Query results (list) | query.observe() + setState(results) or derived structures | Yes (new array/refs) |

| Model passed as prop | withObservables(['model'], ({ model }) => ({ model: model.observe() })) | Yes (HOC uses new state shape) |

---

Resources

  • DeepWiki: [Nozbe/WatermelonDB](https://github.com/Nozbe/WatermelonDB) โ€”

findAndObserve, observe(), withObservables, model updates.

  • Project: PhraseDetailScreen, LessonDetailScreen, SetDetailScreen

(findAndObserve + wrapper); AttemptCard (withObservables).