QUESTPIE
ClientTanstack query

Query keys

Every builder writes its own key. Knowing the four shapes is what lets you invalidate a whole collection, or exactly one route call, without guessing.

View markdown

You never write a key by hand, but you do have to invalidate. That means knowing what a key looks like and where to cut it.

The four shapes

Collection read  [...prefix, 'collections', name, op, locale, stage, options]
Global read      [...prefix, 'globals',     name, op, locale, stage, options]
Any write        [...prefix, branch,        name, op, locale, stage]
Route query      [...prefix, 'routes', ...segments, 'query', locale, input]

prefix is ['questpie'] unless you changed it. branch is collections or globals. op is the builder name: find, count, findOne, findVersions, create, update and so on.

The locale and stage slots are always there, holding undefined when you did not set them. That is why a prefix match is the safe way to invalidate.

Two keys sit outside those shapes. A route mutation key ends at 'mutation', locale, and routes have no stage slot at all. A channel key is [...prefix, 'channels', name, 'subscription' | 'presence', args], with neither slot.

Invalidating

q.key(parts) is the one helper. It puts your prefix on the front and hands back a QueryKey.

// Every posts query: all operations, all options, all locales.
queryClient.invalidateQueries({ queryKey: q.key(["collections", "posts"]) });

// Only the find queries on that collection.
queryClient.invalidateQueries({
	queryKey: q.key(["collections", "posts", "find"]),
});

For an exact match, take the key off the builder you already called. Every query options object carries its own queryKey.

const { queryKey } = q.collections.posts.find({ where: { published: true } });
queryClient.invalidateQueries({ queryKey });

Routes have a third way. .key(input) rebuilds the query key without building the query, which suits prefetching too.

queryClient.invalidateQueries({
	queryKey: q.routes.dashboard.getStats.post.key({ period: "week" }),
});

`.key()` is a route thing

Collections, globals and channels have no .key() builder. Use top-level q.key([...]) for a partial match, or read queryKey off the options object for an exact one.

What gets stripped

Options are sanitized before they land in a key, so a non-serializable value cannot break React Query's structural comparison.

In your optionsIn the key
a function valuedropped
an undefined valuedropped
a null valuekept
no options at all{}

Two calls differing only by a function share a cache entry

Both keys sanitize to the same thing, so both queries read and write the same entry. Never encode a discriminator as a function inside read options.

Mutation keys

A mutation key stops before the variables. create() on posts is always ['questpie', 'collections', 'posts', 'create', locale, stage], whatever you pass to mutate(). Mutations do not invalidate anything on their own, so do it in onSuccess.

const create = useMutation({
	...q.collections.posts.create(),
	onSuccess: () =>
		queryClient.invalidateQueries({
			queryKey: q.key(["collections", "posts"]),
		}),
});

Changing the prefix

keyPrefix moves every key at once. Pass [] and keys start at 'collections' with nothing in front. See Configuration, which also covers what a proxy-level locale or stage puts in those two slots.

On this page