Search code examples
reactjstypescriptreact-querytanstackreact-query

React Query - Use response from list fetch to `setQueryData` for each item?


I am fetching a list of items to render in a listing page - let's call them Todos. The fetch is done via a custom react-query hook that caches the response under the ['todo-list'] queryKey. When I fetch the list, I get all of the data for each Todo in the response. I then render the list by mapping the response to the <Todo> component, passing the item as a prop.

The problem I am having is that these items are rendered in several different places, and each place is using a different queryKey. So where I have ['todo-list'], I also have ['my-todo-list'], ['all-todo-list'], etc. These Todo items can be "completed", which triggers a mutation call to the back-end but optimistically updates (via setQueryData) the individual item so the UI reflects the change immediately without having to wait for the mutation to resolve, and the list query to be refetched. Making these optimistic updates across multiple lists of Todos is fairly complex and feels inefficient.

My question is, would it make sense to use the response from any Todo list query to setQueryData for the individual items? For example, setQueryData(['todo', todo.id], todo). Then any child components that need to render that data can just get the Todo object from the react-query cache, with only the id field being passed as a prop.

Is this considered an anti-pattern or bad practice in anyway?

Here is a basic example of the todo-list query:

// custom react-query hook
export const useTodos = () => {
  const todoQuery = useQuery(
    ['todo-list'],
    () => {
      return fetchTodos()
    }
  )

  // Set the todo data in the cache
  const queryClient = useQueryClient()
  todoQuery.data?.todos?.forEach((todo) => {
    queryClient.setQueryData(['todo', todo.id], todo)
  })

  return todoQuery
}

This is a very basic example of the usage in the Todo component:

// Todo component
function Todo({id}) {
  const {data, isLoading} = useTodoById(id)
  if (isLoading) {
    return <p>Loading...</p>
  }

  return (
    <h1 className="todo">{todo.title}</h1>
  )
}

export default Todo

It is also worth noting, the useTodoById hook is also a custom react-query hook that fetches a single Todo by it's id - the value is cached under the ['todo', todo.id] queryKey. It has a staleTime set for 10 minutes.


Solution

  • What you're suggesting is not a bad practice at all, in fact, it is mentioned on TkDodo's blog (react-query mantainer). You can do it push-based or pull-based, both have pros and cons that are explained in the blog. The only bad practice I'm seeing is your queryKeys, it's often recommended you do things like ['todos', 'lists'] and ['todos', 'detail', 1] so you can take advantage of invalidateQuery partial matching. I'd recommend checking the effective query keys blog post