From 8f041590d945574aec56c8d8648984acb741753b Mon Sep 17 00:00:00 2001 From: ekzyis Date: Wed, 31 Jan 2024 05:52:50 +0100 Subject: [PATCH] refactor: Use fragments to undo cache updates --- components/invoice.js | 39 ++++++++++++++++++++++++++++++++------- components/item-act.js | 20 ++++++++++---------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/components/invoice.js b/components/invoice.js index 2179e8f93..d86ea1466 100644 --- a/components/invoice.js +++ b/components/invoice.js @@ -217,14 +217,39 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => { } const inv = data.createInvoice + // If this is a zap, we need to manually be optimistic to have a consistent + // UX across custodial and WebLN zaps since WebLN zaps don't call GraphQL + // mutations which implement optimistic responses natively. + // Therefore, we check if this is a zap and then wrap the WebLN payment logic + // with manual cache update calls. + const itemId = optimisticResponse?.act?.id + const isZap = !!itemId + let _update + if (isZap && update) { + _update = () => { + const fragment = { + id: `Item:${itemId}`, + fragment: gql` + fragment ItemMeSats on Item { + sats + meSats + } + ` + } + const item = client.cache.readFragment(fragment) + update(client.cache, { data: optimisticResponse }) + // undo function + return () => client.cache.writeFragment({ ...fragment, data: item }) + } + } + // wait until invoice is paid or modal is closed const modalClose = await waitForPayment({ invoice: inv, showModal, provider, pollInvoice, - updateCache: () => update?.(client.cache, { data: optimisticResponse }), - undoUpdate: () => update?.(client.cache, { data: { ...optimisticResponse }, undo: true }) + gqlCacheUpdate: _update }) const retry = () => onSubmit( @@ -267,10 +292,10 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => { } const INVOICE_CANCELED_ERROR = 'invoice was canceled' -const waitForPayment = async ({ invoice, showModal, provider, pollInvoice, updateCache, undoUpdate }) => { +const waitForPayment = async ({ invoice, showModal, provider, pollInvoice, gqlCacheUpdate }) => { if (provider.enabled) { try { - return await waitForWebLNPayment({ provider, invoice, pollInvoice, updateCache, undoUpdate }) + return await waitForWebLNPayment({ provider, invoice, pollInvoice, gqlCacheUpdate }) } catch (err) { const INVOICE_CANCELED_ERROR = 'invoice was canceled' // check for errors which mean that QR code will also fail @@ -293,12 +318,13 @@ const waitForPayment = async ({ invoice, showModal, provider, pollInvoice, updat }) } -const waitForWebLNPayment = async ({ provider, invoice, pollInvoice, updateCache, undoUpdate }) => { +const waitForWebLNPayment = async ({ provider, invoice, pollInvoice, gqlCacheUpdate }) => { + let undoUpdate try { // try WebLN provider first return await new Promise((resolve, reject) => { // be optimistic and pretend zap was already successful for consistent zapping UX - updateCache?.() + undoUpdate = gqlCacheUpdate?.() // can't use await here since we might be paying HODL invoices // and sendPaymentAsync is not supported yet. // see https://www.webln.guide/building-lightning-apps/webln-reference/webln.sendpaymentasync @@ -333,7 +359,6 @@ const waitForWebLNPayment = async ({ provider, invoice, pollInvoice, updateCache }, 1000) }) } catch (err) { - // undo attempt to make zapping UX consistent undoUpdate?.() console.error('WebLN payment failed:', err) throw err diff --git a/components/item-act.js b/components/item-act.js index 355062a3f..574d18b17 100644 --- a/components/item-act.js +++ b/components/item-act.js @@ -107,14 +107,14 @@ export function useAct ({ onUpdate } = {}) { const me = useMe() const update = useCallback((cache, args) => { - const { data: { act: { id, sats, path, act, amount } }, undo } = args + const { data: { act: { id, sats, path, act } } } = args cache.modify({ id: `Item:${id}`, fields: { sats (existingSats = 0) { if (act === 'TIP') { - return existingSats + (undo ? -amount : sats) + return existingSats + sats } return existingSats @@ -122,7 +122,7 @@ export function useAct ({ onUpdate } = {}) { meSats: me ? (existingSats = 0) => { if (act === 'TIP') { - return existingSats + (undo ? -amount : sats) + return existingSats + sats } return existingSats @@ -131,7 +131,7 @@ export function useAct ({ onUpdate } = {}) { meDontLikeSats: me ? (existingSats = 0) => { if (act === 'DONT_LIKE_THIS') { - return existingSats + (undo ? -amount : sats) + return existingSats + sats } return existingSats @@ -148,7 +148,7 @@ export function useAct ({ onUpdate } = {}) { id: `Item:${aId}`, fields: { commentSats (existingCommentSats = 0) { - return existingCommentSats + (undo ? -amount : sats) + return existingCommentSats + sats } } }) @@ -173,7 +173,7 @@ export function useAct ({ onUpdate } = {}) { export function useZap () { const update = useCallback((cache, args) => { - const { data: { act: { id, sats, path, amount } }, undo } = args + const { data: { act: { id, sats, path } } } = args // determine how much we increased existing sats by by checking the // difference between result sats and meSats @@ -191,15 +191,15 @@ export function useZap () { const satsDelta = sats - item.meSats - if (satsDelta >= 0) { + if (satsDelta > 0) { cache.modify({ id: `Item:${id}`, fields: { sats (existingSats = 0) { - return existingSats + (undo ? -amount : satsDelta) + return existingSats + satsDelta }, meSats: () => { - return undo ? sats - amount : sats + return sats } } }) @@ -211,7 +211,7 @@ export function useZap () { id: `Item:${aId}`, fields: { commentSats (existingCommentSats = 0) { - return existingCommentSats + (undo ? -amount : satsDelta) + return existingCommentSats + satsDelta } } })