@leancodepl/react-query-cqrs-client
v10.1.2
Published
TanStack Query integration for CQRS commands and queries
Readme
@leancodepl/react-query-cqrs-client
TanStack Query CQRS client with hooks for queries, operations, and commands.
Features
- TanStack Query integration - Built-in caching, optimistic updates, and background refetching
- CQRS pattern - Separate queries, commands, and operations with proper typing
- Custom hooks - Hook factories for all operation types with loading states
- Error handling - Validation errors with custom error codes and handlers
- Authentication - Token handling with automatic refresh integration
- Cache management - Smart invalidation and query dependency management
Installation
npm install @leancodepl/react-query-cqrs-client
# or
yarn add @leancodepl/react-query-cqrs-clientAPI
mkCqrsClient(cqrsEndpoint, queryClient, tokenProvider, ajaxOptions, tokenHeader)
Creates TanStack Query CQRS client with hooks for queries, operations, and commands.
Parameters:
cqrsEndpoint: string- Base URL for CQRS API endpointsqueryClient: QueryClient- TanStack Query client instancetokenProvider?: Partial<TokenProvider>- Optional token provider for authenticationajaxOptions?: Omit<AjaxConfig, ...>- Optional RxJS Ajax configuration optionstokenHeader?: string- Header name for authentication token (default: "Authorization")
Returns: Object with createQuery, createOperation, and createCommand hook factories
Usage Examples
Basic Setup
import { mkCqrsClient } from "@leancodepl/react-query-cqrs-client"
import { QueryClient } from "@tanstack/react-query"
const queryClient = new QueryClient()
const client = mkCqrsClient({
cqrsEndpoint: "https://api.example.com",
queryClient,
tokenProvider: {
getToken: () => Promise.resolve(localStorage.getItem("token")),
},
})Query Hook
import React from 'react';
interface GetUserQuery {
userId: string;
}
interface UserResult {
id: string;
name: string;
email: string;
}
const useGetUser = client.createQuery<GetUserQuery, UserResult>('GetUser');
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = useGetUser({ userId });
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading user</div>;
return (
<div>
<h1>{data?.name}</h1>
<p>{data?.email}</p>
</div>
);
}Command Hook
import React from 'react';
interface CreateUserCommand {
name: string;
email: string;
}
const errorCodes = { EmailExists: 1, InvalidEmail: 2 } as const;
const useCreateUser = client.createCommand<CreateUserCommand, typeof errorCodes>('CreateUser', errorCodes);
function CreateUserForm() {
const { mutate: createUser, isPending } = useCreateUser({
handler: (handle) =>
handle('success', () => 'User created successfully')
.handle('EmailExists', () => 'Email already exists')
.handle('failure', () => 'Failed to create user')
.check(),
});
const handleSubmit = () => {
createUser({ name: 'John', email: '[email protected]' });
};
return (
<button onClick={handleSubmit} disabled={isPending}>
{isPending ? 'Creating...' : 'Create User'}
</button>
);
}Operation Hook
interface UploadFileOperation {
file: File;
folder: string;
}
interface UploadResult {
url: string;
filename: string;
}
const useUploadFile = client.createOperation<UploadFileOperation, UploadResult>('UploadFile');
function FileUploader() {
const { mutate: uploadFile, isPending } = useUploadFile({
invalidateQueries: [['GetFiles']],
});
const handleUpload = (file: File) => {
uploadFile({ file, folder: 'documents' });
};
return (
<input
type="file"
onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])}
disabled={isPending}
/>
);
}