about summary refs log tree commit diff
path: root/src/lib/async
diff options
context:
space:
mode:
authorAnsh <anshnanda10@gmail.com>2023-04-19 13:58:24 -0700
committerGitHub <noreply@github.com>2023-04-19 15:58:24 -0500
commit1472bd4f173a483e5f1a91514244fecaec23808f (patch)
treefc33a3e90d4024939ef022a2c468f7b58df5fc37 /src/lib/async
parentbe83d2933cfce1035573bc14108a87451cf48c2d (diff)
downloadvoidsky-1472bd4f173a483e5f1a91514244fecaec23808f.tar.zst
#420: add updateDataOptimistically utility to disallow like counter out of sync (#446)
* add isLikedPressed flag to disallow like counter out of sync

* create revertible helper for updateDataOptimistically

* test implementation

* Update updateDataOptimistically() and apply to reposts

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Diffstat (limited to 'src/lib/async')
-rw-r--r--src/lib/async/revertible.ts52
1 files changed, 52 insertions, 0 deletions
diff --git a/src/lib/async/revertible.ts b/src/lib/async/revertible.ts
new file mode 100644
index 000000000..3c8e3e8f9
--- /dev/null
+++ b/src/lib/async/revertible.ts
@@ -0,0 +1,52 @@
+import {runInAction} from 'mobx'
+import {deepObserve} from 'mobx-utils'
+import set from 'lodash.set'
+
+const ongoingActions = new Set<any>()
+
+export const updateDataOptimistically = async <
+  T extends Record<string, any>,
+  U,
+>(
+  model: T,
+  preUpdate: () => void,
+  serverUpdate: () => Promise<U>,
+  postUpdate?: (res: U) => void,
+): Promise<void> => {
+  if (ongoingActions.has(model)) {
+    return
+  }
+  ongoingActions.add(model)
+
+  const prevState: Map<string, any> = new Map<string, any>()
+  const dispose = deepObserve(model, (change, path) => {
+    if (change.observableKind === 'object') {
+      if (change.type === 'update') {
+        prevState.set(
+          [path, change.name].filter(Boolean).join('.'),
+          change.oldValue,
+        )
+      } else if (change.type === 'add') {
+        prevState.set([path, change.name].filter(Boolean).join('.'), undefined)
+      }
+    }
+  })
+  preUpdate()
+  dispose()
+
+  try {
+    const res = await serverUpdate()
+    runInAction(() => {
+      postUpdate?.(res)
+    })
+  } catch (error) {
+    runInAction(() => {
+      prevState.forEach((value, path) => {
+        set(model, path, value)
+      })
+    })
+    throw error
+  } finally {
+    ongoingActions.delete(model)
+  }
+}