Ferry
Examples

SWR

Ferry and SWR in one app, each with its own job:

  • Ferry holds the rows the app creates, edits and syncs. They are read locally, with no loading state.
  • SWR fetches what lives only on a server: another API, or numbers the server computes from the synced rows.

It uses the schema and client of the Quick start. SWR needs no provider, so the app only has Ferry's SyncProvider.

Local rows next to fetched data

Ferry's hook is useQuery, or useLiveQuery if the name reads better next to useSWR:

trip.tsxClient
import { useLiveQuery } from 'ferrysync/react';
import useSWR from 'swr';

import { sync } from './sync';

export const Trip = ({ projectId }: { projectId: string }) => {
  // Local: there on the first render, offline too.
  const tasks = useLiveQuery(sync.tasks.where({ projectId, done: false }));
  // Remote: fetched, cached, and loading until it answers.
  const weather = useSWR(['weather', projectId], () => fetchWeather());

  return (
    <section>
      <p>{weather.isLoading ? 'Checking the weather…' : weather.data}</p>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>{task.title}</li>
        ))}
      </ul>
    </section>
  );
};

Data the server computes from synced rows

A report the server builds from the tasks changes when the tasks reach the server. Put lastSyncId in the key: it grows only when the device catches up with the server's log, so the report refetches when the server may have news, and not on every render or every local write.

report.tsxClient
import { useSyncStatus } from 'ferrysync/react';
import useSWR from 'swr';

import { sync } from './sync';

export const Report = ({ projectId }: { projectId: string }) => {
  const { lastSyncId } = useSyncStatus(sync);
  const report = useSWR(['report', projectId, lastSyncId], () => fetchReport(projectId), {
    // Keep showing the last report while the next one loads.
    keepPreviousData: true,
  });

  if (report.data === undefined) {
    return <p>Loading the report…</p>;
  }
  return (
    <p>
      {report.data.done} done, {report.data.open} open
    </p>
  );
};

Gotchas

  • Do not wrap Ferry's reads in useSWR. It adds a second cache that every write and every change from another device would have to revalidate, and a loading state for data that is already in memory.
  • Do not write through useSWRMutation. sync.tasks.update(…) applies at once and waits in the outbox when offline.
  • SWR revalidates on focus; Ferry does not need to. Its realtime connection keeps the rows current. To push and pull at once when the app comes back, call sync.syncNow(): see Status and offline.

On this page