watermelondb
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@writermethods), the observable emits the same object reference with updated properties — it does not emit a new model instance.
React useState bailout
useStateusesObject.isto 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, updatephraseStatewith 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, sosetState(results)usually triggers re-renders.- If you build derived data (e.g.
linked.filter(...),assignments) in the subscribe callback andsetStatethat, 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 intosetState(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(), ... })). SeeAttemptCardinfeatures/lesson/components/AttemptCard.tsx. - For route params (e.g.
id) you typically subscribe manually inuseEffect(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; thencollection.findAndObserve(id)orquery.observe().subscribe(...). - Schema/tables:
database/schema.ts; collection access viadb.collections.get<Model>(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 —
findAndObserve,observe(),withObservables, model updates. - Project:
PhraseDetailScreen,LessonDetailScreen,SetDetailScreen(findAndObserve + wrapper);AttemptCard(withObservables).
More from jchaselubitz/drill-app
expo-router
Patterns for Expo Router including Stack configuration, native tabs, and file-based routing best practices. Apply when working with navigation, routing, or screen configuration.
106expo-audio
Guide for using expo-audio to implement audio playback and recording in React Native apps. Apply when working with audio features, sound playback, recording, or text-to-speech functionality.
17expo-glass-effect
Guide for using expo-glass-effect to create iOS native liquid glass UI effects. Apply when implementing glass effect UI components, cards, or surfaces on iOS.
9zod-v4-patterns
Ensures Zod v4 patterns are used correctly throughout the codebase. Apply when creating or modifying validation schemas, form schemas, or any Zod validators. Enforces v4 syntax and prevents deprecated v3 patterns.
5rn-button-component
Ensures buttons use the unified Button component with proper state management, icon support, and glass effect integration. Apply when creating or modifying buttons in the app.
5gemini-api
Patterns for using Google Gemini API with structured output, JSON mode, and proper configuration. Apply when implementing AI features, text generation, or working with Gemini models.
4