#Offline transactions testing

6 messages · Page 1 of 1 (latest)

regal sundial
#

How are you using them?

regal sundial
#

Also be sure to play around with the example app

cosmic imp
#

Thanks, I will take a look to the example app tomorrow. My tests are quite simple, I just don't refetch the collection on success and update the cache manually.

Collection

export const organizationCollection = createCollection(
  queryCollectionOptions({
    queryClient,
    queryKey: organizationQueries.getAll().queryKey,
    queryFn: organizationQueries.getAll().queryFn,
    getKey: (organization) => organization.id,
    onUpdate: async ({ transaction }) => {
      const responses = await Promise.all(
        transaction.mutations.map((mutation) =>
          organizationMutations.updateOne({
            id: mutation.original.id,
            changes: mutation.changes
          })
        )
      );
      return { refetch: false };
    }
  })
);
#

Offline executor

export const organizationOffline = startOfflineExecutor({
  collections: { 
    organization: organizationCollection 
  },
  mutationFns: {
    syncOrganizations: async ({ transaction, idempotencyKey }) => {
      for (const mutation of transaction.mutations) {
        switch (mutation.type) {
          case 'update': {
            const response = await organizationMutations.updateOne({
              id: mutation.original.id,
              changes: mutation.changes
            });

            // Server directly return data
            if (response.success && response.data) {
              organizationCollection.utils.writeBatch(() => {
                response.data.forEach((serverItem) => {
                  organizationCollection.utils.writeUpdate(serverItem);
                });
              });

              // Manually update cache
              queryClient.setQueryData(['organization'], (oldData) => {
                if (!oldData) return response.data;
                return oldData.map(org => {
                  const updated = response.data.find(item => item.id === org.id);
                  return updated || org;
                });
              });
            }
            break;
          }
        }
      }
    }
  },
  onLeadershipChange: (isLeader) => {
    if (!isLeader) {
      console.warn('Running in online-only mode (another tab is the leader)');
    }
  }
});
#

Component

export default function Test() {
  const { data: orgs } = useLiveQuery((q) =>
    q.from({ org: organizationCollection })
  );

  async function testOfflineUpdate(orgId) {
    const tx = organizationOffline.createOfflineTransaction({
      mutationFnName: 'syncOrganizations',
      autoCommit: true
    });

    tx.mutate(() => {
      organizationCollection.update(orgId, (draft) => {
        const newBrandName = new Date().toLocaleTimeString();
        draft.brandName = newBrandName;
      });
    });
  }

  return (
    <button type="button" onClick={() => testOfflineUpdate(orgs[0].id)}>
      Test Update Offline
    </button>
  );
}
cosmic imp
#

I did a deep dive into the example todo.
Can someone explain to me what is the point of this condition?

if (typeof actions.toggleTodo === `function`)

From TodoDemo.tsx,
https://github.com/TanStack/db/blob/main/examples/react/offline-transactions/src/components/TodoDemo.tsx

  const handleToggleTodo = async (id: string) => {
    try {
      setError(null)
      if (typeof actions.toggleTodo === `function`) {
        await actions.toggleTodo(id)
      } else {
        actions.toggleTodo(id)
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : `Failed to toggle todo`)
    }
  }
GitHub

The reactive client store for your API. Contribute to TanStack/db development by creating an account on GitHub.