Concepts
useQuery or a state library?
Ferry's data is on the device, so any UI can read it. There are four ways to do that, and they are not rivals: they read the same local database. The difference is where your app already keeps its state.
sync.tasks.where(...)
|
+---------------+-----+---------+------------------+
| | | |
useQuery() @ferrysync/zustand @ferrysync/jotai @ferrysync/legend-state
(React hook) (a Zustand store) (Jotai atoms) (Legend-State observables)Which one to pick
| Your app… | Use | Why |
|---|---|---|
| is new, or uses React with no global state library | useQuery | Nothing to install. Reads exactly what a component needs and re-renders only when it changes |
| already reads its state through Zustand selectors | @ferrysync/zustand | The tables join the store you have, next to your own state |
| already builds on Jotai atoms | @ferrysync/jotai | The tables and the status are atoms, so derived atoms can use them |
| already uses Legend-State observables | @ferrysync/legend-state | You bind tables into observables you own |
| is not React (Vue, Svelte, Solid, vanilla) | get and subscribe | Two methods on every query are all a binding needs |
When unsure, start with useQuery. Moving to an adapter later changes how a component reads, not
the data or the writes.
What does not change
Whichever you pick:
- Writes are the same. Always call the client:
sync.tasks.update(id, …). The adapters are read-only mirrors, so never write to them withsetState,setor an observable'sset. - There is no loading state per read. The data is already on the device.
sync.readysays once when local data is open:SyncProviderwaits for it, the Zustand and Jotai adapters expose it asready, and Legend-State bindings fill in as the data loads. - Sync status is
sync.status.useSyncStatus()reads it in React, and the Zustand and Jotai adapters mirror it asstatus.
What does change
useQuery | Zustand, Jotai, Legend-State | |
|---|---|---|
| You describe | A query: filters, order, limit | Whole tables, mirrored by id |
| Filtering and sorting | In the query | In your selector, derived atom or computed observable |
| Fits | Screens that show a slice of the data | Apps that already pass state around through one store |
Using both
Nothing stops an app from using useQuery on most screens and an adapter where a store already
exists: they share the one client, so they always agree.
import { useQuery } from 'ferrysync/react';
import { sync } from './sync';
const OpenCount = () => {
const open = useQuery(sync.tasks.where({ done: false }));
return <p>{open.length} open</p>;
};Next
- React: the hooks in full.
- Zustand, Jotai and Legend-State.
- Your own bindings for any other framework.