query-stuff v0.2.0
๐ Table of Contents
- ๐ฏ Motivation
- ๐ฆ Installation
- ๐ Features
- ๐ญ factory
- ๐ฅ query
- ๐ค mutation
- ๐ input
- ๐ฆ group
- ๐ use - ๐งช Experimental
- ๐ useExactMutationState
- ๐๏ธ middlewareBuilder - ๐งช Experimental
- ๐ง extend - ๐งช Experimental
- ๐งฌ inherit - ๐งช Experimental
- ๐ญ factory
- ๐ Credits
- ๐ License
๐ฏ Motivation
query-stuff builds on top of ideas from TkDodoโs blog, particularly Effective React Query Keys and The Query Options API.
๐ฆ Installation
query-stuff requires @tanstack/react-query v5 and above as a peerDependency.
npm i @tanstack/react-query query-stuff๐ Features
๐ญ factory
The
factoryfunction is the core of query-stuff. It provides a structured way to define queries, mutations, and groups without manually managing their respective keys. It is recommended to have afactoryfor each feature as stated in the Use Query Key factories section of the Effective React Query blog.Setup:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ /* Creates an empty todos factory */ }));Usage:
console.log(todos._key); // ["todos"]โ Options
name:string- Required
- The name of the feature.
builderFn:- Required
- A function that receives a builder and returns an object containing queries, mutations, and groups.
๐ฌ Note
namewill be used as the base key for all queries, mutations, and groups within the factory.
๐ฅ query
Setup:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ read: q.query( async () => { /*Fetch all todos*/ }, { staleTime: 0, gcTime: 5 * 60 * 1000, }, ), }));Usage:
useQuery(todos.read()); console.log(todos.read().queryKey); // ["todos", "read"]โ Options
queryFn:({ ctx: Context | void, input: Input | void }) => Promise<Data>- Required
- The function that the query will use to request data.
options:- Optional
- Additional options for the query.
queryFnandqueryKeyare handled internally and should not be included here. Refer to the official useQuery docs for all the available options.
๐ฌ Note
queryKeyis automatically generated for each query.
๐ค mutation
Setup:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ // ... delete: q.mutation( async () => { //Delete all todos }, { onMutate: () => { //onMutate }, onSuccess: () => { //onSuccess }, onError: () => { //onError }, }, ), }));Usage:
useMutation(todos.delete()); console.log(todos.delete().mutationKey); // ["todos", "delete"]โ Options
mutationFn:({ ctx: Context| void, input: Input | void }) => Promise<Data>- Required
- A function that performs an asynchronous task and returns a promise.
options:- Optional
- Additional options for the mutation.
mutationFnandmutationKeyare handled internally and should not be included here. Refer to official useMutation docs for all the available options.
๐ฌ Note
mutationKeyis automatically generated for each mutation.
๐ input
Setup:
import { factory } from "query-stuff"; import { z } from "zod"; const todos = factory("todos", (q) => ({ // ... todo: q.input(z.object({ id: z.number() })).query(({ input }) => { /*Fetch a todo*/ }), }));Usage:
useQuery(todos.todo({ id: 1 })); console.log(todos.todo({ id: 1 }).queryKey); // ["todos", "todo", { id: 1 }]โ Options
schema:Schema | RecordSchema- Required
- A Standard Schema compliant schema.
๐ฌ Note
- Refer to the official "What schema libraries implement the spec?" docs for compatible schema libraries.
RecordSchemacan used as input forquery,mutationandgroup.Schemacan used as input forqueryandmutationonly.
๐ฆ group
Setup:
import { factory } from "query-stuff"; import { z } from "zod"; const todos = factory("todos", (q) => ({ // ... todo: q.input(z.object({ id: z.number() })).group((q) => ({ read: q.query(({ ctx }) => { /*Fetch todo*/ }), delete: q.mutation(({ ctx }) => { /*Delete todo*/ }); })), }));Usage:
useQuery(todos.todo({ id: 1 }).read()); console.log(todos.todo({ id: 1 }).read().queryKey); // ["todos", "todo", { id: 1 }, "read"] useMutation(todos.todo({ id: 1 }).delete()); console.log(todos.todo({ id: 1 }).delete().mutationKey); // ["todos", "todo", { id: 1 }, "delete"] console.log(todos.todo._key); // ["todos", "todo"] console.log(todos.todo({ id: 1 })._key); // ["todos", "todo", { id: 1 }]โ Options
builderFn:- Required
- A function that receives a builder and returns an object containing queries, mutations, and groups.
๐ฌ Note
groupwith aninputcan only created with aRecordSchema.
๐ use (๐งช Experimental)
The
usefunction allows composing middlewares that wrap outgoing functions, such asqueryFnfor queries andmutationFnfor mutations.๐งช Experimental
- This feature is experimental and prefixed with
unstable_. - This API may change in future versions.
Setup/Usage:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ // ... todo: q .unstable_use(async ({ next, ctx }) => { // Before const start = Date.now(); const result = await next({ ctx: { /*Extended context */ }, }); const end = Date.now(); console.log(`Elapsed time: ${end - start}`); // After return result; }) .group((q) => ({ // ... })), }));โ Options
middlewareFn:- Required
- A function that receives a
nextfunction and actxobject, then returns the result from callingnext. ctxis the incoming context object.nextis a function that accepts an object with an extendedctxand returns the result of the execution chain.
๐ฌ Note
- The
nextfunction can be used to extend the outgoing context with a newctxobject. - The result of the
nextfunction must be returned.
- This feature is experimental and prefixed with
๐ useExactMutationState
The
useExactMutationStatehook is built on top of React Query's useMutationState hook. TheuseExactMutationStatehook provides a type-safe API for tracking mutations for a givenmutationKeyprovided byquery-stuff.Setup:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ // ... delete: q.mutation(async () => { //Delete all todos }), }));Usage:
useMutation(todos.delete()); useExactMutationState({ filters: { mutationKey: todos.delete().mutationKey, }, select: (mutation) => mutation.state.data, });โ Options
options:- filters:
MutationFilters- Required
- mutationKey:
- Required
- The
mutationKeyfor a given mutation. Must be retrieved fromquery-stuff
- Additional mutation filters:
- Optional
- Refer to the official Mutation Filters docs for the available options.
- filters:
select:(mutation: Mutation) => TResult- Optional
- Use this to transform the mutation state.
๐ฌ Note
- The
mutationKeymust be retrieved directly fromquery-stuff. - The
exactfilter is set to true by default.
๐๏ธ middlewareBuilder (๐งช Experimental)
๐งช Experimental
- This feature is experimental and prefixed with
unstable_. - This API may change in future versions.
The
middlewareBuilderallows defining reusable, standalone middleware functions that can be plugged into theusefunction.Setup:
import { unstable_middlewareBuilder } from "query-stuff"; const { middleware: fooMiddleware } = unstable_middlewareBuilder( async ({ next }) => { return await next({ ctx: { foo: "foo", }, }); }, );Before:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ read: q .unstable_use(async ({ next }) => { return await next({ ctx: { foo: "foo", }, }); }) .query(async ({ ctx }) => { console.log(ctx); // { foo: "foo" } // Fetch all todos }), }));After:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ read: q.unstable_use(fooMiddleware).query(async ({ ctx }) => { console.log(ctx); // { foo: "foo" } // Fetch all todos }), }));โ Options
middlewareFn:- Required
- A function that receives a
nextfunction and actxobject, then returns the result from callingnext. ctxis the incoming context object.nextis a function that accepts an object with an extendedctxand returns the result of the execution chain.
๐ฌ Note
- The
nextfunction can be used to extend the outgoing context with a newctxobject. - The result of the
nextfunction must be returned.
- This feature is experimental and prefixed with
๐ง extend (๐งช Experimental)
๐งช Experimental
- This feature is experimental and prefixed with
unstable_. - This API may change in future versions.
The
extendfunction allows you to build upon an existing standalone middleware, adding additional transformations while preserving previous ones.Setup:
import { unstable_middlewareBuilder } from "query-stuff"; const { middleware: fooMiddleware } = unstable_middlewareBuilder( async ({ next }) => { return await next({ ctx: { foo: "foo", }, }); }, ); const { middleware: fooBarMiddleware } = unstable_middlewareBuilder( fooMiddleware, ).unstable_extend(async ({ next, ctx }) => { console.log(ctx); // { foo: "foo" } return await next({ ctx: { bar: "bar", }, }); });Before:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ read: q .unstable_use(async ({ next }) => { return await next({ ctx: { foo: "foo", }, }); }) .unstable_use(async ({ next, ctx }) => { console.log(ctx); // { foo: "foo" } return await next({ ctx: { bar: "bar", }, }); }) .query(async ({ ctx }) => { console.log(ctx); // { foo: "foo", bar: "bar" } // Fetch all todos }), }));After:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ read: q.unstable_use(fooBarMiddleware).query(async ({ ctx }) => { console.log(ctx); // { foo: "foo", bar: "bar" } // Fetch all todos }), }));โ Options
middlewareFn:- Required
- A function that receives a
nextfunction and actxobject, then returns the result from callingnext. ctxis the incoming context object.nextis a function that accepts an object with an extendedctxand returns the result of the execution chain.
๐ฌ Note
- The
nextfunction can be used to extend the outgoing context with a newctxobject. - The result of the
nextfunction must be returned. extendstacks middlewares in the order they are defined, ensuring each middleware in the chain is executed sequentially.
- This feature is experimental and prefixed with
๐งฌ inherit (๐งช Experimental)
๐งช Experimental
- This feature is experimental and prefixed with
unstable_. - This API may change in future versions.
The
inheritfunction allows you to build a new middleware that is anticipated as an upcoming middleware with respect to a given source middleware. This allows you to create standalone middlewares while maintaining type safety and context awareness.Setup:
import { unstable_middlewareBuilder } from "query-stuff"; const { middleware: fooMiddleware } = unstable_middlewareBuilder( async ({ next }) => { return await next({ ctx: { foo: "foo", }, }); }, ); const { middleware: barMiddleware } = unstable_middlewareBuilder( fooMiddleware, ).unstable_inherit(async ({ next, ctx }) => { console.log(ctx); // { foo: "foo" } return await next({ ctx: { bar: "bar", }, }); });Before:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ read: q .unstable_use(async ({ next }) => { return await next({ ctx: { foo: "foo", }, }); }) .unstable_use(async ({ next, ctx }) => { console.log(ctx); // { foo: "foo" } return await next({ ctx: { bar: "bar", }, }); }) .query(async ({ ctx }) => { console.log(ctx); // { foo: "foo", bar: "bar" } // Fetch all todos }), }));After:
import { factory } from "query-stuff"; const todos = factory("todos", (q) => ({ read: q .unstable_use(fooMiddleware) .unstable_use(barMiddleware) .query(async ({ ctx }) => { console.log(ctx); // { foo: "foo", bar: "bar" } // Fetch all todos }), }));โ Options
middlewareFn:- Required
- A function that receives a
nextfunction and actxobject, then returns the result from callingnext. ctxis the incoming context object.nextis a function that accepts an object with an extendedctxand returns the result of the execution chain.
๐ฌ Note
- The
nextfunction can be used to extend the outgoing context with a newctxobject. - The result of the
nextfunction must be returned. inheritdoes not modify the middleware execution order.- It must only be used to ensure type safety and context awareness when composing middleware.
- This feature is experimental and prefixed with
๐ Credits
As mentioned in the motivation section, this library enforces best practices outlined in TkDodoโs blog, particularly Effective React Query Keys and The Query Options API.
The API design is heavily inspired by tRPC, particularly its use of the builder pattern and intuitive method conventions.
๐ License
MIT ยฉ MlNl-PEKKA