Leveraging the Question Operate Context




All of us attempt to enhance as engineers, and as time goes by, we hopefully achieve that endeavour. Perhaps we be taught new issues that invalidate or problem our earlier pondering. Or we realise that patterns that we thought splendid wouldn’t scale to the extent we now want them to.

Fairly a while has handed since I first began to make use of React Question. I believe I realized an ideal deal on that journey, and I’ve additionally “seen” quite a bit. I would like my weblog to be as up-to-date as doable, as a way to come again right here and re-read it, realizing that the ideas are nonetheless legitimate. That is now extra related than ever since Tanner Linsley agreed to hyperlink to my weblog from the official React Query documentation.

That is why I’ve determined to put in writing this addendum to my Effective React Query Keys article. Please be certain that to learn it first to have an understanding of what we’re speaking about.



Scorching take

Do not use inline capabilities – leverage the Question Operate Context given to you, and use a Question Key manufacturing unit that produces object keys

Inline capabilities are by far the best strategy to move parameters to your queryFn, as a result of they allow you to closure over different variables out there in your customized hook. Let’s take a look at the evergreen todo instance:

sort State = 'all' | 'open' | 'finished'
sort Todo = {
  id: quantity
  state: TodoState
}
sort Todos = ReadonlyArray<Todo>

const fetchTodos = async (state: State): Promise<Todos> => {
  const response = await axios.get(`todos/${state}`)
  return response.information
}

export const useTodos = () => {
  // think about this grabs the present consumer choice
  // from someplace, e.g. the url
  const { state } = useTodoParams()

  // ✅ The queryFn is an inline operate that
  // closures over the handed state
  return useQuery(['todos', state], () => fetchTodos(state))
}
Enter fullscreen mode

Exit fullscreen mode

Perhaps you acknowledge the instance – It is a slight variation of #1: Practical React Query – Treat the query key like a dependency array. This works nice for easy examples, nevertheless it has a fairly substantial downside when having a lot of parameters. In larger apps, it isn’t remarkable to have a lot of filter and sorting choices, and I’ve personally seen as much as 10 params being handed.

Suppose we need to add sorting to our question. I wish to method these items from the underside up – beginning with the queryFn and letting the compiler inform me what I want to vary subsequent:

sort Sorting = 'dateCreated' | 'identify'
const fetchTodos = async (
  state: State,
  sorting: Sorting
): Promise<Todos> => {
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.information
}
Enter fullscreen mode

Exit fullscreen mode

It will definitely yield an error in our customized hook, the place we name fetchTodos, so let’s repair that:

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  // 🚨 can you notice the error ⬇️
  return useQuery(['todos', state], () => fetchTodos(state, sorting))
}
Enter fullscreen mode

Exit fullscreen mode

Perhaps you’ve got already noticed the problem: Our queryKey bought out of sync with our precise dependencies, and no crimson squiggly strains are screaming at us about it 😔. Within the above case, you may doubtless spot the problem very quick (hopefully through an integration take a look at), as a result of altering the sorting doesn’t robotically set off a refetch. And, let’s be trustworthy, it is also fairly apparent on this easy instance. I’ve nevertheless seen the queryKey diverge from the precise dependencies a few occasions within the final months, and with higher complexity, these can lead to some onerous to trace points. There’s additionally a purpose why React comes with the react-hooks/exhaustive-deps eslint rule to keep away from that.

So will React Question now include its personal eslint-rule 👀 ?

Properly, that will be one choice. There’s additionally the babel-plugin-react-query-key-gen
that solves this downside by producing question keys for you, together with all of your dependencies. React Question nevertheless comes with a unique, built-in approach of dealing with dependencies: The QueryFunctionContext.



QueryFunctionContext

The QueryFunctionContext is an object that’s handed as argument to the queryFn. You’ve got most likely used it earlier than when working with infinite queries:

// that is the QueryFunctionContext ⬇️
const fetchProjects = ({ pageParam = 0 }) =>
  fetch('/api/initiatives?cursor=' + pageParam)

useInfiniteQuery('initiatives', fetchProjects, {
  getNextPageParam: (lastPage) => lastPage.nextCursor,
})
Enter fullscreen mode

Exit fullscreen mode

React Question makes use of that object to inject details about the question to the queryFn. In case of infinite queries, you may get the return worth of getNextPageParam injected as pageParam.

Nevertheless, the context additionally accommodates the queryKey that’s used for this question (and we’re about so as to add extra cool issues to the context), which implies you truly do not should closure over issues, as they are going to be offered for you by React Question:

const fetchTodos = async ({ queryKey }) => {
  // 🚀 we are able to get all params from the queryKey
  const [, state, sorting] = queryKey
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.information
}

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  // ✅ no must move parameters manually
  return useQuery(['todos', state, sorting], fetchTodos)
}
Enter fullscreen mode

Exit fullscreen mode

With this method, you mainly don’t have any approach of utilizing any further parameters in your queryFn with out additionally including them to the queryKey 🎉.



sort the QueryFunctionContext

One of many ambitions for this method was to get full sort security and infer the kind of the QueryFunctionContext from the queryKey handed to useQuery. This wasn’t straightforward, however React Question helps that since v3.13.3. For those who inline the queryFn, you may see that the kinds are correctly inferred (thanks, Generics):

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  return useQuery(
    ['todos', state, sorting] as const,
    async ({ queryKey }) => {
      const response = await axios.get(
        // ✅ that is protected as a result of the queryKey is a tuple
        `todos/${queryKey[1]}?sorting=${queryKey[2]}`
      )
      return response.information
    }
  )
}
Enter fullscreen mode

Exit fullscreen mode

That is good and all, however nonetheless has a bunch of flaws:

  • You possibly can nonetheless simply use no matter you’ve gotten within the closure to construct your question
  • Utilizing the queryKey for constructing the url within the above approach remains to be unsafe as a result of you may stringify all the pieces.



Question Key Factories

That is the place question key factories are available once more. If we’ve got a typesafe question key manufacturing unit to construct our keys, we are able to use the return sort of that manufacturing unit to sort our QueryFunctionContext. Here is how that may look:

const todoKeys = {
  all: ['todos'] as const,
  lists: () => [...todoKeys.all, 'list'] as const,
  checklist: (state: State, sorting: Sorting) =>
    [...todoKeys.lists(), state, sorting] as const,
}

const fetchTodos = async ({
  queryKey,
}: // 🤯 solely settle for keys that come from the manufacturing unit
QueryFunctionContext<ReturnType<typeof todoKeys['list']>>) => {
  const [, , state, sorting] = queryKey
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.information
}

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  // ✅ construct the important thing through the manufacturing unit
  return useQuery(todoKeys.checklist(state, sorting), fetchTodos)
}
Enter fullscreen mode

Exit fullscreen mode

The sort QueryFunctionContext is exported by React Question. It takes one generic, which defines the kind of the queryKey. Within the above instance, we set it to be equal to regardless of the checklist operate of our key manufacturing unit returns. Since we use const assertions, all our keys might be strictly typed tuples – so if we attempt to use a key that does not conform to that construction, we are going to get a sort error.



Object Question Keys

Whereas slowly transitioning to the above method, I observed that array keys will not be actually performing that nicely. This turns into obvious when taking a look at how we destruct the question key now:

const [, , state, sorting] = queryKey
Enter fullscreen mode

Exit fullscreen mode

We mainly miss the primary two components (our hardcoded scopes todo and checklist) and solely use the dynamic components. After all, it did not take lengthy till we added one other scope originally, which once more led to wrongly constructed urls:

Supply: A PR I lately made

Seems, objects clear up this downside rather well, as a result of you should utilize named destructuring. Additional, they’ve no downside when used inside a question key, as a result of fuzzy matching for question invalidation works the identical for objects as for arrays. Take a look on the partialDeepEqual operate if you happen to’re concerned about how that works.

Protecting that in thoughts, that is how I’d assemble my question keys with what I do know immediately:

const todoKeys = {
  // ✅ all keys are arrays with precisely one object
  all: [{ scope: 'todos' }] as const,
  lists: () => [{ ...todoKeys.all[0], entity: 'checklist' }] as const,
  checklist: (state: State, sorting: Sorting) =>
    [{ ...todoKeys.lists()[0], state, sorting }] as const,
}

const fetchTodos = async ({
  // ✅ extract named properties from the queryKey
  queryKey: [{ state, sorting }],
}: QueryFunctionContext<ReturnType<typeof todoKeys['list']>>) => {
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.information
}

export const useTodos = () => {
  const { state, sorting } = useTodoParams()

  return useQuery(todoKeys.checklist(state, sorting), fetchTodos)
}
Enter fullscreen mode

Exit fullscreen mode

Object question keys even make your fuzzy matching capabilities extra highly effective, as a result of they don’t have any order. With the array method, you may deal with all the pieces todo associated, all todo lists, or the todo checklist with a particular filter. With objects keys, you are able to do that too, but in addition deal with all lists (e.g. todo lists and profile lists) if you wish to:

// 🕺 take away all the pieces associated to the todos characteristic
queryClient.removeQueries([{ scope: 'todos' }])

// 🚀 reset all todo lists
queryClient.resetQueries([{ scope: 'todos', entity: 'list' }])

// 🙌 invalidate all lists throughout all scopes
queryClient.invalidateQueries([{ entity: 'list' }])
Enter fullscreen mode

Exit fullscreen mode

This could are available fairly useful when you’ve got a number of overlapping scopes which have a hierarchy, however the place you continue to need to match all the pieces belonging to the sub-scope.



Is that this price it?

As all the time: it relies upon. I have been loving this method recently (which is why I wished to share it with you), however there’s definitely a tradeoff right here between complexity and kind security. Composing question keys inside the important thing manufacturing unit is barely extra advanced (as a result of queryKeys nonetheless should be an Array on the prime degree), and typing the context relying on the return sort of the important thing manufacturing unit can be not trivial. In case your group is small, your api interface is slim and / otherwise you’re utilizing plain JavaScript, you may not need to go that route. As per ordinary, select whichever instruments and approaches take advantage of sense on your particular scenario 🙌


That is it for immediately. Be at liberty to succeed in out to me on twitter
when you’ve got any questions, or simply depart a remark beneath ⬇️





Abu Sayed is the Best Web, Game, XR and Blockchain Developer in Bangladesh. Don't forget to Checkout his Latest Projects.


Checkout extra Articles on Sayed.CYou

#Leveraging #Question #Operate #Context