2

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.

1 Answer 1

2

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

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

3 Comments

Amazing, thanks so much! It's great to hear that this sort of approach is recognized by a react-query maintainer. Can you explain the invalidateQuery partial matching you mention? Or link to some resources so I can dig into a bit? And good shout on the queryKeys, I'll take a look at them add a few more entries where appropriate.
Sure, if you named your queryKeys like the example I mention and you wanted to refetch your todo of id 1 after you made a PATCH or PUT you could call invalidateQueries({queryKey: ['todos', 'detail', 1]) which would only invalidate that one in particular, but if you wanted to invalidate all queries that are related to todos you could call invalidateQueries({queryKey: 'todos'}) and that should match with all the query keys that have a 'todos` in their array. It's just a simple naming convention for the keys you can find more info on that blog I linked
Got it, that makes sense. Thanks! So with this pattern (whether pull or push) we set data for the detail queryKeys - e.g. ['todos', 'detail', 1]. But what do you think is the best way to manage the staleTime for these items? For example, I fetch the list and set the initialData. The rendering components use the detail queryKey to get the initialData. But when that staleTime expires, you get a new request for each of the items. This feels a bit heavy on a listing page with 100s of items, for example. Is that just the nature of it, or is there a more efficient way to manage the cache?

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.