React Query: clean way of writing multiple useMutation custom hooks?

Viewed 2490

I've been using the useMutation() hook for crud tasks for my app, and I feel that I'm writing a lot of repeated code, initially my code was like this:

const [createList] = useCreateList(reset, err => setError(err.message));
const [updateList] = useCreateList(reset, err => setError(err.message));
const [deleteList] = useCreateList(reset, err => setError(err.message));

Where the reset function is this:

const reset = () => {
    setQsButtonStatus(null); 
    setSelectedList(null);
    setTitleValue("");
}

The desired end code I would want is this:

const {createList, updateList, deleteList} = useListMutation(reset, err => setError(err.message))

I can make this work but to me the code for it looks very messy (again a lot of repeated code):

import * as List from "./list-api";

const useListMutation = (onSettled, onError) => {

    const [createList] = useMutation(List.createList, {
        onMutate: (listTitle) => {
            queryCache.cancelQueries("lists");
            const current = queryCache.getQueryData("lists");
            queryCache.setQueryData("lists", prev => [...prev, {title: listTitle, id: uuidv4()}])
            return () => queryCache.setQueryData("lists", current);
        },
        onError: (err, variables, rollback) => {rollback(); onError(err)},
        onSettled: () => {
            queryCache.invalidateQueries("lists");
            onSettled();
        }
    })

    const [updateList] = useMutation(List.updateList, {
        onMutate: ({id, titleValue}) => {
            queryCache.cancelQueries("lists");
            const current = queryCache.getQueryData("lists");
            queryCache.setQueryData("lists", prev => [
                ...prev.filter(list => list._id !== id),
                {_id: id, title: titleValue}
            ])
            return () => queryCache.setQueryData("lists", current);
        },
        onError: (err, variables, rollback) => {rollback(); onError(err)},
        onSettled: () => {
            queryCache.invalidateQueries("lists");
            onSettled();
        }
    })

    const [deleteList] = useMutation(List.deleteList, {
        onMutate: (id) => {
            queryCache.cancelQueries("lists");
            const current = queryCache.getQueryData("lists");
            queryCache.setQueryData("lists", prev => [
                ...prev.filter(list => list._id !== id),
            ])
            return () => queryCache.setQueryData("lists", current);
        },
        onError: (err, variables, rollback) => {rollback(); onError(err)},
        onSettled: () => {
            queryCache.invalidateQueries("lists");
            onSettled();
        }
    })


    return {createList, updateList, deleteList};

}

export default useListMutation; 

Is there a better way to do this?

1 Answers

One way of reducing the duplicated code would be to create a getOptions function which returns the onMutate, onError, and onSettled functions. This would look something like this:

const useListMutation = (onSettled, onError) => {
  const getOptions = (onMutate) => ({
    onMutate(data) {
      queryCache.cancelQueries("lists")
      const current = queryCache.getQueryData("lists")
      onMutate(data)
      return () => queryCache.setQueryData("lists", current)
    },
    onError(err, variables, rollback) {
      rollback()
      onError(err)
    },
    onSettled() {
      queryCache.invalidateQueries("lists")
      onSettled()
    },
  })

  const [createList] = useMutation(
    List.createList,
    getOptions((listTitle) => {
      queryCache.setQueryData("lists", (prev) => [
        ...prev,
        { title: listTitle, id: uuidv4() },
      ])
    })
  )

  const [updateList] = useMutation(
    List.updateList,
    getOptions(({ id, titleValue }) => {
      queryCache.setQueryData("lists", (prev) => [
        ...prev.filter((list) => list._id !== id),
        { _id: id, title: titleValue },
      ])
    })
  )

  ...
}
Related