Просмотр исходного кода

pull characterByIdQuery from characterListQuery cache

Jason Gorst 1 месяц назад
Родитель
Сommit
6ddd373d22

+ 91 - 0
app/mutations/characters/useCreateCharacter.js

@@ -0,0 +1,91 @@
+// noinspection JSIgnoredPromiseFromCall
+
+export const useCreateCharacter = defineMutation(() => {
+  const queryCache = useQueryCache()
+
+  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
+      }
+
+      console.log("[useCreateCharacter] [onMutate]", 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)
+
+      // 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 })
+
+      // pass the old and new character list, options,  and the new character to the other hooks
+      return { oldCharacterList, newCharacterList, oldOptions, newCharacter }
+    },
+
+    mutation: (character) => {
+      console.log("[useCreateCharacter] [mutation]", character)
+
+      return useEmit("character:create", character)
+    },
+
+    // on both error and success
+    onSettled(_data, _errors, _vars, { 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 })
+      }
+    },
+
+    onError(error, _vars, { 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)) {
+        console.log("[useCreateCharacter] [onError] [rollback]")
+
+        queryCache.setQueryData(characterListQuery.key, oldCharacterList)
+        queryCache.setQueryData(characterOptionsQuery.key, oldOptions)
+      }
+
+      // handle the error
+      console.error("[useCreateCharacter] [onError]", error)
+    },
+
+    onSuccess(character, _vars, { newCharacter }) {
+      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)
+      }
+    }
+  })
+
+  return {
+    ...mutation,
+    createCharacter: (character) => mutate(character)
+  }
+})

+ 69 - 0
app/mutations/characters/useDeleteCharacter.js

@@ -0,0 +1,69 @@
+// noinspection JSIgnoredPromiseFromCall
+
+export const useDeleteCharacter = defineMutation(() => {
+  const queryCache = useQueryCache()
+
+  const { mutate, ...mutation } = useMutation({
+    onMutate(id) {
+      // 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 })
+
+      let oldOptions, oldCharacter, newCharacterList
+
+      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 })
+
+        // 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 })
+      }
+
+      // pass the id and the old and new character lists to the other hooks
+      return { id, oldCharacter, oldCharacterList, newCharacterList, oldOptions }
+    },
+
+    mutation: (id) => useEmit("character:delete", id),
+
+    onSettled(_data, _error, _vars, { 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 }) {
+      // 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)) {
+        queryCache.setQueryData(characterListQuery.key, oldCharacterList)
+        queryCache.setQueryData(characterOptionsQuery.key, oldOptions)
+        queryCache.setQueryData(characterByIdQuery(id).key, oldCharacter)
+      }
+
+      // handle the error
+      console.error("[useDeleteCharacter] [onError]", err)
+    },
+
+    onSuccess() {}
+  })
+
+  return {
+    ...mutation,
+    deleteCharacter: (id) => mutate(id)
+  }
+})

+ 74 - 0
app/mutations/characters/useUpdateCharacter.js

@@ -0,0 +1,74 @@
+// noinspection JSIgnoredPromiseFromCall
+
+export const useUpdateCharacter = defineMutation(() => {
+  const queryCache = useQueryCache()
+
+  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 })
+      }
+
+      // 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),
+
+    onSettled(_data, _error, _vars, { id, newCharacter }) {
+      // invalidate the queries to refetch the new data
+      if (newCharacter) {
+        queryCache.invalidateQueries({ key: characterListQuery.key })
+        queryCache.invalidateQueries({ key: characterByIdQuery(id).key })
+      }
+    },
+
+    onError(err, _vars, { id, 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)) {
+        queryCache.setQueryData(characterListQuery.key, oldCharacterList)
+        queryCache.setQueryData(characterOptionsQuery.key, oldOptions)
+        queryCache.setQueryData(characterByIdQuery(id).key, oldCharacter)
+      }
+
+      // handle the error
+      console.error("[useUpdateCharacter] [onError]", err)
+    },
+
+    onSuccess() {}
+  })
+
+  return {
+    ...mutation,
+    updateCharacter: (id, updates) => mutate({ id, updates })
+  }
+})

+ 22 - 1
app/queries/character.js

@@ -2,7 +2,8 @@
 
 export const characterQueryKeys = {
   root: ["characterList"],
-  options: () => [...characterQueryKeys.root, "options"]
+  options: () => [...characterQueryKeys.root, "options"],
+  byId: (id) => ["character", id]
 }
 
 export const characterListQuery = defineQueryOptions({
@@ -14,3 +15,23 @@ export const characterOptionsQuery = defineQueryOptions({
   key: characterQueryKeys.options,
   query: () => useEmit("character:options")
 })
+
+export const characterByIdQuery = defineQueryOptions((id) => ({
+  key: characterQueryKeys.byId(id),
+
+  query: async () => {
+    const { data: characterList } = await usePrefetchCharacterList()
+
+    if (_isNil(characterList) || _isEmpty(characterList)) {
+      throw new QueryError("error fetching characterList", { key: characterQueryKeys.byId(id) })
+    }
+
+    const character = _find(characterList, { id })
+
+    if (_isNil(character)) {
+      throw new QueryError(`character with id ${id} not found`, { key: characterQueryKeys.byId(id) })
+    }
+
+    return character
+  }
+}))