reactjsnext.jsreact-querytanstackreact-querytrpc

Invalidating an Inactive Query in tRPC within T3 Stack


I'm using the T3 Stack with tRPC and facing a challenge with query invalidation across different routes. My application has two routes: /profile and /student-profile.

After performing a mutation (accepting or rejecting the student) on /student-profile, I need to invalidate the task list data on /profile to update it accordingly. However, even though the mutation is successful, the invalidation does not trigger, as confirmed by my logs.

I realized that the "list" query becomes inactive when navigating to /student-profile, preventing the invalidation.

Here's my constraint: I want to avoid setting refetchOnMount: true or cacheTime: 0 for this query. The reason is to prevent making an additional network request every time the /profile page is visited. This is crucial for reducing unnecessary network traffic and improving user experience.

My question is: How can I effectively invalidate an inactive query (the list on /profile) from another route (/student-profile`) in this setup, while adhering to my constraint of not frequently refetching the data? Is there a specific approach in tRPC within the T3 Stack to handle such a scenario?

Any guidance or insights on this issue would be very helpful.

Thank you in advance!

/profile route

const {data: tasks, isFetching} = api.tasks.getInterestedTasksByAuthorId.useQuery({
    authorId: user.id
}, {
    refetchOnMount: false,
    refetchOnWindowFocus: false,
});

/student-profile route

const acceptStudent = api.tasks.acceptStudent.useMutation({
        async onSuccess() {
            try {
                await utils.tasks.getInterestedTasksByAuthorId.invalidate()
            } catch (e) {
                console.error(e)
            }
        },
    });

const handleAcceptStudent = async () => {
        await acceptStudent.mutateAsync({
            studentId,
            taskId
        })
    }

Solution

  • How can I effectively invalidate an inactive query

    There are a couple of ways to do this directly in your mutation callback:

    utils.tasks.getInterestedTasksByAuthorId.invalidate(undefined, { refetchType: 'all' })
    

    Note that in trpc, the filters are the second param, so if you have no input, you need to pass undefined as the first param.

    utils.tasks.getInterestedTasksByAuthorId.refetch()
    

    The problem with both approaches is that they just target everything they match. This might work for your case, but let's consider that your todo items can be filtered or searched. Because of the document cache that react-query has, you will have multiple cache entries (one per input) that will match. So if you have 3 potential todo-items lists in your cache, all being inactive, they will all refetch on the spot, even though the user might never go back to them.

    That's why invalidation is usually preferred. It will only mark items as stale that are inactive, and will only refetch them just-in-time when the user requests them the next time.

    The reason is to prevent making an additional network request every time the /profile page is visited.

    The thing is: you don't need to tweak flags like refetchOnMount for that. Setting a high staleTime for your resource is the preferred approach. If you set staleTime to 20 minutes, that means data will be considered fresh for 20 minutes, so every call to useQuery will only read data from the cache in this timeframe, because the flags like refetchOnMount and refetchOnWindowFocus etc will only ever refetch data that is considered stale when the event comes up.

    And that also plays very will with invalidation. Because even if you have staleTime set to Infinity (meaning: it will always be fresh), calling invalidateQueries() essentially overwrites that.

    So my preferred approach is to just leave all flags on their default settings, customize staleTime to my liking and just use the default invalidate() method, because it's the least amount of code to write and maintain, the best possible user experience and it will never unnecessarily overfetch.