ari-cakeA
Convex Community5mo ago
4 replies
ari-cake

Easier Optimistic Updates

Hi, just wanted to share a small helper for optimistic updates.

First, here's some example usage:

  // With this helper
  useOptimisticMutation(api.docs.setContent)
    .withUptimisticQuery(api.docs.getContent, (value, qArgs, mutArgs) => {
      if (qArgs.doc == mutArgs.doc) {
        value.content = mutArgs.content;
      }
    })
    .withUptimisticQuery(api.docs.get, (value, _, mutArgs) => {
      for (const p of value.page) {
        if (p._id == mutArgs.doc) {
          p.title = getTitle(mutArgs.content);
        }
      }
    });



The helper is the withOptimisticQuery function - you get the return value of all queries, and can easily just mutate the state - even if you don't know the exact args, etc.

It's all 100% TypeSafe, too, and it avoids unnecessary updates. Thanks to immer it also does treat everything as immutable, but you can still use it as you're used to :)

For comparision, here's the mutation with "plain" convex:

// BEFORE, without the function
useMutation(api.docs.setContent).withOptimisticUpdate((ctx, mutArgs) => {
    const docContent = ctx.getQuery(api.docs.getContent, { doc: mutArgs.doc });
    if (docContent) {
      ctx.setQuery(
        api.docs.getContent,
        { doc: mutArgs.doc },
        {
          ...docContent,
          content: mutArgs.content,
        },
      );
    }

    const newTitle = getTitle(mutArgs.content);
    // Alright, let's iterate all document lists
    for (const entry of ctx.getAllQueries(api.docs.get)) {
      if (!entry.value) {
        continue;
      }

      for (const doc of entry.value.page) {
        if (doc._id == mutArgs.doc) {
          // now, the fun begins - creating an update :)
          const update = {
            ...entry.value,
            page: entry.value.page.with(entry.value.page.indexOf(doc), {
              ...doc,
              title: newTitle,
            }),
          } satisfies (typeof entry)["value"];

          ctx.setQuery(api.docs.get, entry.args, update);
        }
      }
    }


Anyway, needless to say, I prefer the new version. Funnily enough, the "before" version also likely causes more re-renders. immer doesn't create an optimistic update if the old and new title are the same. In my code, I think I'd have to check myself
Was this page helpful?