1

So I'm making a kanban board style task manager using react and react query. My current implementation of the data fetching is like the following:

const { data } = useQuery('listCollection', getListCollection)

and the content of data is something like this:

// data
{
  listOrder: number[]
  lists: IList[]
}

interface IList {
  id: number
  title: string
  todoOrder: number[]
  todos: ITodo[]
}

interface ITodo {
  id: number
  text: string
  checked: boolean
}

So basically a list collection contains multiple lists and each lists contain multiple todos.

Now, I want this application to do optimistic update on each mutation (add a new todo, delete, check a todo, etc).

Here is my current implementation of optimistic update when toggling a todo check:

const mutation = useMutation(
    (data: { todoId: number; checked: boolean }) =>
      editTodo(data),
    {
      onMutate: async (data) => {
        await queryClient.cancelQueries('listCollection')

        const previousState = queryClient.getQueryData('listCollection')

        queryClient.setQueryData('listCollection', (prev: any) => ({
          ...prev,
          lists: prev.lists.map((list: IList) =>
            list.todoOrder.includes(data.todoId)
              ? {
                  ...list,
                  todos: list.todos.map((todo) =>
                    todo.id === data.todoId
                      ? { ...todo, checked: data.checked }
                      : todo,
                  ),
                }
              : list,
          ),
        }))

        return { previousState }
      },
      onError: (err, newTodo, context) => {
        queryClient.setQueryData(parent, context?.previousState)
      },
      onSuccess: () => queryClient.invalidateQueries(parent),
    },
  )

As you can see, that's overly complicated. How should I approach this?

2
  • The dev of react-query is pretty active in answering questions, maybe look through the Qs on his blog and leave a reply at the bottom if you can’t find anything about it Commented Feb 16, 2023 at 13:11
  • 1
    tbh, I don't think this is "overly complicated". It's rather explicit. This is also independent of react-query, the question is merely: How do I immutably update deeply nested data, and the question is as old as JS itself. Yes, you can do this with immer if you want, but you need to explicitly wrap it. FYI, in v5, we'll have an alternative way to do optimistic updates that will not need to write to the cache. Commented Feb 26, 2023 at 12:53

1 Answer 1

6

The best way to update the deeply nested data in react query is by using "Immer" library. It is very light weight and it uses proxies to change the reference of only updated data, and reducing the cost of rendering for non-updated data.

import produce from "immer";

const mutation = useMutation(
    (data: { todoId: number; checked: boolean }) =>
      editTodo(data),
    {
      onMutate: async (data) => {
        await queryClient.cancelQueries('listCollection')

        const previousState = queryClient.getQueryData('listCollection')
        
        const updData = produce(previousState, (draftData) => {
        // Destructing the draftstate of data.
          let {lists} = draftData;
          lists = lists.map((list: IList) => {
          // Mapping through lists and checking if id present in todoOrder and todo.
            if(list.todoOrder.includes(data.todoId) && list.todo[data.id]){
              list.todo[data.id].checked = data.checked;
            }
            return list.
          }
          // Updating the draftstate with the modified values
          draftData.lists = lists;
       }
       // Setting query data.
       queryClient.setQueryData("listCollection", updData);

        return { previousState }
      },
      onError: (err, newTodo, context) => {
        queryClient.setQueryData(parent, context?.previousState)
      },
      onSuccess: () => queryClient.invalidateQueries(parent),
    },
  )

This will solve your case. You can modify the listOrder if needed just the way I updates lists.

Sign up to request clarification or add additional context in comments.

1 Comment

Agreed that this can help make the state change better. The redux docs have some good walkthroughs about doing a similar thing with their reducers: redux-toolkit.js.org/usage/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.