Skip to content

Normalized list updates

When a mutation changes one item in a list query, you usually don't need a full refetch — patch the item in place with update (or optimisticUpdate).

Patch an item by id

ts
import { update } from 'effector-refetch';

// todosQuery.$data: Todo[]
update({
  query: todosQuery,
  on: toggleTodoMutation, // returns the updated Todo
  fn: ({ data, result: updated }) => (data ?? []).map((todo) => (todo.id === updated.id ? updated : todo)),
});

Remove an item

ts
update({
  query: todosQuery,
  on: deleteTodoMutation, // params: id
  fn: ({ data, params: id }) => (data ?? []).filter((todo) => todo.id !== id),
});

Optimistic toggle with rollback

ts
import { optimisticUpdate } from 'effector-refetch';

optimisticUpdate({
  query: todosQuery,
  on: toggleTodoMutation,
  update: ({ data, params: id }) => (data ?? []).map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
  // on success, reconcile with the server's version
  commit: ({ data, result: updated }) => (data ?? []).map((t) => (t.id === updated.id ? updated : t)),
});

Paginated lists (createInfiniteQuery)

update / optimisticUpdate accept an infinite query too — there data is the array of pages, so map over the pages and patch the item in place (no refetch, no flicker):

ts
update({
  query: todosInfinite, // createInfiniteQuery(...)
  on: toggleTodoMutation,
  fn: ({ data: pages, result: id }) =>
    (pages ?? []).map((page) => ({
      ...page,
      items: page.items.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
    })),
});

The same shape works with optimisticUpdate (its update/commit callbacks also receive the page array). Patches go through the query's __.setData seam, so they're scope-correct.

MIT Licensed