Browse Source

add mutations without optimistic updates for websocket events

Jason Gorst 3 weeks ago
parent
commit
1a1d8768b0

+ 13 - 0
app/composables/useEventHandlers.js

@@ -0,0 +1,13 @@
+export default function useEventHandlers() {
+  const {
+    $socketio: { socket }
+  } = useNuxtApp()
+
+  const { createCharacterFromEvent } = useCreateCharacter()
+  const { deleteCharacterFromEvent } = useDeleteCharacter()
+  const { updateCharacterFromEvent } = useUpdateCharacter()
+
+  socket.on("character:create", createCharacterFromEvent)
+  socket.on("character:delete", deleteCharacterFromEvent)
+  socket.on("character:update", updateCharacterFromEvent)
+}

+ 49 - 44
app/mutations/characters/useCreateCharacter.js

@@ -3,61 +3,70 @@
 export const useCreateCharacter = defineMutation(() => {
   const queryCache = useQueryCache()
 
+  // noinspection JSUnusedLocalSymbols
   const { mutate, ...mutation } = useMutation({
-    onMutate(character) {
-      // save the current character list and options
-      const oldCharacterList = queryCache.getQueryData(characterListQuery.key)
-      const oldOptions = queryCache.getQueryData(characterOptionsQuery.key)
-
-      // create the new character
-      const newCharacter = {
-        ...character,
-        id: Number.MAX_SAFE_INTEGER
-      }
+    onMutate({ character, optimisticUpdates }) {
+      if (optimisticUpdates) {
+        // save the current character list and options
+        const oldCharacterList = queryCache.getQueryData(characterListQuery.key)
+        const oldOptions = queryCache.getQueryData(characterOptionsQuery.key)
+
+        // create the new character
+        const newCharacter = {
+          ...character,
+          id: Number.MAX_SAFE_INTEGER
+        }
 
-      console.log("[useCreateCharacter] [onMutate]", newCharacter)
+        console.log("[useCreateCharacter] [onMutate]", newCharacter)
 
-      // create a copy of the current character list with the new character
-      const newCharacterList = [...oldCharacterList || [], newCharacter]
+        // create a copy of the current character list with the new character
+        const newCharacterList = [...oldCharacterList || [], newCharacter]
 
-      // calculate new character options
-      const newOptions = getCharacterOptions(newCharacterList)
+        // calculate new character options
+        const newOptions = getCharacterOptions(newCharacterList)
 
-      // update the caches with the new character list, options, and character
-      queryCache.setQueryData(characterListQuery.key, newCharacterList)
-      queryCache.setQueryData(characterOptionsQuery.key, newOptions)
+        // update the caches with the new character list, options, and character
+        queryCache.setQueryData(characterListQuery.key, newCharacterList)
+        queryCache.setQueryData(characterOptionsQuery.key, newOptions)
 
-      // cancel (without refreshing) queries depending on the character list
-      queryCache.cancelQueries({ key: characterListQuery.key })
+        // cancel (without refreshing) queries depending on the character list
+        queryCache.cancelQueries({ key: characterListQuery.key })
 
-      // pass the old and new character list, options,  and the new character to the other hooks
-      return { oldCharacterList, newCharacterList, oldOptions, newCharacter }
+        // pass the old and new character list, options,  and the new character to the other hooks
+        return { oldCharacterList, newCharacterList, oldOptions, newCharacter }
+      }
     },
 
-    mutation: (character) => {
+    mutation: ({ character, optimisticUpdates = true }) => {
       console.log("[useCreateCharacter] [mutation]", character)
 
       return useEmit("character:create", character)
     },
 
     // on both error and success
-    onSettled(_data, _errors, _vars, { newCharacter }) {
+    onSettled(_data, _errors, { optimisticUpdates }, { newCharacter }) {
       console.log("[useCreateCharacter] [onSettled]")
 
-      // invalidate queries to refetch the new data
-      queryCache.invalidateQueries({ key: characterListQuery.key })
-
-      // newCharacter can be undefined if the onMutate hook fails
-      if (newCharacter) {
-        queryCache.cancelQueries({ key: characterByIdQuery(newCharacter.id).key })
-        queryCache.remove({ key: characterByIdQuery(newCharacter.id).key })
+      if (optimisticUpdates) {
+        // invalidate queries to refetch the new data
+        queryCache.invalidateQueries({ key: characterListQuery.key })
+
+        // newCharacter can be undefined if the onMutate hook fails
+        if (newCharacter) {
+          // directly cancel queries with a temp id and remove cache entry
+          queryCache.getEntries({ key: characterByIdQuery(newCharacter.id).key, exact: true })
+            .map((entry) => {
+              queryCache.cancel(entry)
+              queryCache.remove(entry)
+            })
+        }
       }
     },
 
-    onError(error, _vars, { oldCharacterList, newCharacterList, oldOptions }) {
+    onError(error, { optimisticUpdates }, { oldCharacterList, newCharacterList, oldOptions }) {
       // before applying the rollback, check if the value in the cache is the same
       //    in case the cache was updated by another mutation or query
-      if (newCharacterList === queryCache.getQueryData(characterListQuery.key)) {
+      if (optimisticUpdates && newCharacterList === queryCache.getQueryData(characterListQuery.key)) {
         console.log("[useCreateCharacter] [onError] [rollback]")
 
         queryCache.setQueryData(characterListQuery.key, oldCharacterList)
@@ -68,24 +77,20 @@ export const useCreateCharacter = defineMutation(() => {
       console.error("[useCreateCharacter] [onError]", error)
     },
 
-    onSuccess(character, _vars, { newCharacter }) {
+    onSuccess(character, { optimisticUpdates }, { oldCharacterList }) {
       console.log("[useCreateCharacter] [onSuccess]", character)
 
-      // update character list with the newly created character returned by the api
-      const characterList = queryCache.getQueryData(characterListQuery.key) || []
-
-      // replace the character created in onMutate() with the one from the server
-      const characterIndex = _findIndex(characterList, { id: newCharacter.id })
-
-      if (characterIndex >= 0) {
-        const characterListCopy = characterList.toSpliced(characterIndex, 1, character)
-        queryCache.setQueryData(characterListQuery.key, characterListCopy)
+      if (optimisticUpdates) {
+        // create a copy of oldCharacterList with newly returned character
+        const characterList = [...oldCharacterList || [], character]
+        queryCache.setQueryData(characterListQuery.key, characterList)
       }
     }
   })
 
   return {
     ...mutation,
-    createCharacter: (character) => mutate(character)
+    createCharacter: (character) => mutate({ character, optimisticUpdates: true }),
+    createCharacterFromEvent: (character) => mutate({ character, optimisticUpdates: false })
   }
 })

+ 33 - 30
app/mutations/characters/useDeleteCharacter.js

@@ -3,60 +3,62 @@
 export const useDeleteCharacter = defineMutation(() => {
   const queryCache = useQueryCache()
 
+  // noinspection JSUnusedLocalSymbols
   const { mutate, ...mutation } = useMutation({
-    onMutate(id) {
-      // save the current character list, and options
-      const oldCharacterList = queryCache.getQueryData(characterListQuery.key)
+    onMutate({ id, optimisticUpdates }) {
+      if (optimisticUpdates) {
+        // save the current character list, and options
+        const oldCharacterList = queryCache.getQueryData(characterListQuery.key)
 
-      // find the index of the deleted character
-      const characterIndex = _findIndex(oldCharacterList, { id })
+        // find the index of the deleted character
+        const characterIndex = _findIndex(oldCharacterList, { id })
 
-      let oldOptions, oldCharacter, newCharacterList
+        let oldOptions, oldCharacter, newCharacterList
 
-      if (characterIndex >= 0) {
-        // save the options and character
-        oldOptions = queryCache.getQueryData(characterOptionsQuery.key)
-        oldCharacter = oldCharacterList[characterIndex]
+        if (characterIndex >= 0) {
+          // save the options and character
+          oldOptions = queryCache.getQueryData(characterOptionsQuery.key)
+          oldCharacter = oldCharacterList[characterIndex]
 
-        // create a copy of the current character list without the deleted character
-        const newCharacterList = _reject(oldCharacterList || [], { id })
+          // create a copy of the current character list without the deleted character
+          const newCharacterList = _reject(oldCharacterList || [], { id })
 
-        // calculate new character options
-        const newOptions = getCharacterOptions(newCharacterList)
+          // calculate new character options
+          const newOptions = getCharacterOptions(newCharacterList)
 
-        // update caches
-        queryCache.setQueryData(characterListQuery.key, newCharacterList)
-        queryCache.setQueryData(characterOptionsQuery.key, newOptions)
-        queryCache.remove({ key: characterByIdQuery(id).key })
+          // update caches
+          queryCache.setQueryData(characterListQuery.key, newCharacterList)
+          queryCache.setQueryData(characterOptionsQuery.key, newOptions)
 
-        // cancel (without refreshing) queries depending on the character
-        queryCache.cancelQueries({ key: characterListQuery.key })
-        queryCache.cancelQueries({ key: characterByIdQuery(id).key })
-      }
+          // cancel (without refreshing) queries depending on the character
+          queryCache.cancelQueries({ key: characterListQuery.key })
+          queryCache.cancelQueries({ key: characterByIdQuery(id).key })
+        }
 
-      // pass the id and the old and new character lists to the other hooks
-      return { id, oldCharacter, oldCharacterList, newCharacterList, oldOptions }
+        // pass the id and the old and new character lists to the other hooks
+        return { oldCharacter, oldCharacterList, newCharacterList, oldOptions }
+      }
     },
 
-    mutation: (id) => useEmit("character:delete", id),
+    mutation: ({ id, optimisticUpdates = true }) => useEmit("character:delete", id),
 
-    onSettled(_data, _error, _vars, { id }) {
+    onSettled(_data, _error, { id }) {
       // invalidate the queries to refetch the new data
       queryCache.invalidateQueries({ key: characterListQuery.key })
       queryCache.invalidateQueries({ key: characterByIdQuery(id).key })
     },
 
-    onError(err, _vars, { id, oldCharacter, oldCharacterList, oldOptions, newCharacterList }) {
+    onError(error, { id, optimisticUpdates }, { oldCharacter, oldCharacterList, oldOptions, newCharacterList }) {
       // before applying the rollback, check if the values in the cache are the same
       //    in case the cache was updated by another mutation or query
-      if (newCharacterList === queryCache.getQueryData(characterListQuery.key)) {
+      if (optimisticUpdates && newCharacterList === queryCache.getQueryData(characterListQuery.key)) {
         queryCache.setQueryData(characterListQuery.key, oldCharacterList)
         queryCache.setQueryData(characterOptionsQuery.key, oldOptions)
         queryCache.setQueryData(characterByIdQuery(id).key, oldCharacter)
       }
 
       // handle the error
-      console.error("[useDeleteCharacter] [onError]", err)
+      console.error("[useDeleteCharacter] [onError]", error)
     },
 
     onSuccess() {}
@@ -64,6 +66,7 @@ export const useDeleteCharacter = defineMutation(() => {
 
   return {
     ...mutation,
-    deleteCharacter: (id) => mutate(id)
+    deleteCharacter: (id) => mutate({ id, optimisticUpdates: true }),
+    deleteCharacterFromEvent: (id) => mutate({ id, optimisticUpdates: false })
   }
 })

+ 58 - 41
app/mutations/characters/useUpdateCharacter.js

@@ -3,65 +3,81 @@
 export const useUpdateCharacter = defineMutation(() => {
   const queryCache = useQueryCache()
 
+  // noinspection JSUnusedLocalSymbols
   const { mutate, ...mutation } = useMutation({
-    onMutate({ id, updates }) {
-      // save the current character and character list
-      const oldCharacterList = queryCache.getQueryData(characterListQuery.key)
-
-      // find the index of the mutated character
-      const characterIndex = _findIndex(oldCharacterList, { id })
-
-      let oldOptions, oldCharacter, newCharacter, newCharacterList
-
-      if (characterIndex >= 0) {
-        // save the options and character
-        oldOptions = queryCache.getQueryData(characterOptionsQuery.key)
-        oldCharacter = oldCharacterList[characterIndex]
-
-        // create the new character
-        newCharacter = { ...oldCharacter, ...updates }
-
-        // create a copy of the character list with the updated character
-        newCharacterList = oldCharacterList.toSpliced(characterIndex, 1, newCharacter)
-
-        // calculate new character options
-        const newOptions = getCharacterOptions(newCharacterList)
-
-        // update caches
-        queryCache.setQueryData(characterListQuery.key, newCharacterList)
-        queryCache.setQueryData(characterOptionsQuery.key, newOptions)
-        queryCache.remove({ key: characterByIdQuery(id).key })
-
-        // cancel (without refreshing) queries depending on the character
-        queryCache.cancelQueries({ key: characterListQuery.key })
-        queryCache.cancelQueries({ key: characterByIdQuery(id).key })
+    onMutate({ id, updates, optimisticUpdates }) {
+      if (optimisticUpdates) {
+        // save the current character and character list
+        const oldCharacterList = queryCache.getQueryData(characterListQuery.key)
+
+        // find the index of the mutated character
+        const characterIndex = _findIndex(oldCharacterList, { id })
+
+        let oldOptions, oldCharacter, newCharacter, newCharacterList
+
+        if (characterIndex >= 0) {
+          // save the options and character
+          oldOptions = queryCache.getQueryData(characterOptionsQuery.key)
+          oldCharacter = oldCharacterList[characterIndex]
+
+          // create the new character
+          newCharacter = { ...oldCharacter, ...updates }
+
+          // create a copy of the character list with the updated character
+          newCharacterList = oldCharacterList.toSpliced(characterIndex, 1, newCharacter)
+
+          // calculate new character options
+          const newOptions = getCharacterOptions(newCharacterList)
+
+          // update caches
+          queryCache.setQueryData(characterListQuery.key, newCharacterList)
+          queryCache.setQueryData(characterOptionsQuery.key, newOptions)
+
+          // cancel (without refreshing) queries depending on the character
+          queryCache.cancelQueries({ key: characterListQuery.key })
+          queryCache.cancelQueries({ key: characterByIdQuery(id).key })
+        }
+
+        // pass the old and new character, and character lists to the other hooks
+        return {
+          oldCharacter,
+          newCharacter,
+          oldCharacterList,
+          newCharacterList,
+          oldOptions
+        }
       }
-
-      // pass the id, old and new character, and character lists to the other hooks
-      return { id, oldCharacter, newCharacter, oldCharacterList, newCharacterList, oldOptions }
     },
 
-    mutation: ({ id, updates }) => useEmit("character:update", id, updates),
+    mutation: ({ id, updates, optimisticUpdates = true }) =>
+      useEmit("character:update", id, updates),
 
-    onSettled(_data, _error, _vars, { id, newCharacter }) {
+    onSettled(_data, _error, { id, optimisticUpdates }, { newCharacter }) {
       // invalidate the queries to refetch the new data
-      if (newCharacter) {
+      if (!optimisticUpdates || newCharacter) {
         queryCache.invalidateQueries({ key: characterListQuery.key })
         queryCache.invalidateQueries({ key: characterByIdQuery(id).key })
       }
     },
 
-    onError(err, _vars, { id, oldCharacter, oldCharacterList, oldOptions, newCharacterList }) {
+    onError(
+      error,
+      { id, optimisticUpdates },
+      { oldCharacter, oldCharacterList, oldOptions, newCharacterList }
+    ) {
       // before applying the rollback, check if the values in the cache are the same
       //    in case the cache was updated by another mutation or query
-      if (newCharacterList === queryCache.getQueryData(characterListQuery.key)) {
+      if (
+        optimisticUpdates &&
+        newCharacterList === queryCache.getQueryData(characterListQuery.key)
+      ) {
         queryCache.setQueryData(characterListQuery.key, oldCharacterList)
         queryCache.setQueryData(characterOptionsQuery.key, oldOptions)
         queryCache.setQueryData(characterByIdQuery(id).key, oldCharacter)
       }
 
       // handle the error
-      console.error("[useUpdateCharacter] [onError]", err)
+      console.error("[useUpdateCharacter] [onError]", error)
     },
 
     onSuccess() {}
@@ -69,6 +85,7 @@ export const useUpdateCharacter = defineMutation(() => {
 
   return {
     ...mutation,
-    updateCharacter: (id, updates) => mutate({ id, updates })
+    updateCharacter: (id, updates) => mutate({ id, updates, optimisticUpdates: true }),
+    updateCharacterFromEvent: (id, updates) => mutate({ id, updates, optimisticUpdates: false })
   }
 })