mcpField
W
watermelondb

watermelondb

github.com/jchaselubitzDevelopment049 installsUpdated 37w ago
Repository

About watermelondb

A coding skill for WatermelonDB models and observable queries, aimed at developers building React screens backed by database changes. It explains how emissions and object identity affect re-rendering, with patterns for subscriptions and `withObservables`.

Key features

  1. 01Observe records with `findAndObserve`
  2. 02Subscribe to query results
  3. 03Avoid React state reference bailouts
  4. 04Use `withObservables` for reactive props

Use cases

  1. 01Refresh a detail screen after a record update
  2. 02Render lists from changing query results
  3. 03Wire observed model props into a component

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/jchaselubitz/drill-app --skill watermelondb

After installing, type/watermelondbin Claude Code to confirm it loaded.

Bundled filesmeta.yaml

Details

From the project README

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:

const [phraseState, setPhraseState] = useState<
  { phrase: Phrase; _key: number } | null
>(null);

useEffect(() => {
  if (!id) return;
  const sub = db.collections
    .get<Phrase>(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<Model>(TABLE).

Quick reference

ScenarioPatternRe-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 structuresYes (new array/refs)
Model passed as propwithObservables(['model'], ({ model }) => ({ model: model.observe() }))Yes (HOC uses new state shape)

Resources

  • DeepWiki: Nozbe/WatermelonDB — findAndObserve, observe(), withObservables, model updates.
  • Project: PhraseDetailScreen, LessonDetailScreen, SetDetailScreen (findAndObserve + wrapper); AttemptCard (withObservables).

Comments

Markdown supported