@contract-kit/react
v0.1.1
Published
React utilities for combining React Query and React Hook Form with Contract Kit
Maintainers
Readme
@contract-kit/react
React utilities that combine React Query and React Hook Form for Contract Kit.
Installation
npm install @contract-kit/react @contract-kit/react-query @contract-kit/react-hook-formTypeScript Requirements
This package requires TypeScript 5.0 or higher for proper type inference.
Features
- Mutation Forms: Submit a form directly to a mutation contract
- Edit Forms: Query existing data, populate a form, and submit mutations
- Search Forms: Search/filter forms with URL syncing support
- Type-safe: Full TypeScript support with contract inference
- Thin wrapper: Minimal abstraction over React Query and React Hook Form
Usage
Create the Form Query Kit
import { createClient } from '@contract-kit/client';
import { createRQ } from '@contract-kit/react-query';
import { rhf } from '@contract-kit/react-hook-form';
import { createFormQueryKit } from '@contract-kit/react';
const client = createClient({ baseUrl: '/api' });
const rq = createRQ(client);
const kit = createFormQueryKit({ rq, rhf });Mutation Form (Create)
function CreateTodoForm() {
const { form, mutation, onSubmit } = kit(createTodoContract).mutationForm({
form: { defaultValues: { title: '', completed: false } },
mapValuesToParams: (values) => ({ body: values }),
});
return (
<form onSubmit={onSubmit}>
<input {...form.register('title')} />
<button disabled={mutation.isPending}>Create</button>
{mutation.error && <p>Error: {mutation.error.message}</p>}
</form>
);
}Edit Form (Update)
function EditTodoForm({ id }: { id: string }) {
const { form, query, mutation, onSubmit, resetFromQuery } = kit(updateTodoContract).editForm({
context: { id },
query: {
contract: getTodoContract,
params: ({ id }) => ({ path: { id } }),
},
mapQueryDataToValues: (todo) => ({
title: todo.title,
completed: todo.completed,
}),
mapValuesToMutationParams: (values, { id }) => ({
path: { id },
body: values,
}),
resetStrategy: 'onFirstData', // 'never' | 'onFirstData' | 'onEveryDataChange'
});
if (query.isLoading) return <div>Loading...</div>;
if (query.error) return <div>Error loading todo</div>;
return (
<form onSubmit={onSubmit}>
<input {...form.register('title')} />
<button type="button" onClick={resetFromQuery}>Reset</button>
<button disabled={mutation.isPending}>Save</button>
</form>
);
}Search Form (Query)
function TodoSearch() {
const { form, query, onSubmit, searchParams, setSearchParams, resetSearch } =
kit(searchTodosContract).searchForm({
initialSearch: { q: '', completed: undefined },
runMode: 'immediate', // or 'onSubmit'
mapValuesToQueryParams: (values) => ({
query: {
q: values.q,
completed: values.completed,
},
}),
});
return (
<div>
<form onSubmit={onSubmit}>
<input {...form.register('q')} placeholder="Search..." />
<select {...form.register('completed')}>
<option value="">All</option>
<option value="true">Completed</option>
<option value="false">Incomplete</option>
</select>
<button type="submit">Search</button>
<button type="button" onClick={resetSearch}>Reset</button>
</form>
{query.isLoading && <div>Loading...</div>}
{query.data?.items.map((todo) => (
<div key={todo.id}>{todo.title}</div>
))}
</div>
);
}Search Form with URL Syncing
import { createBrowserUrlAdapter } from '@contract-kit/react';
// or for Next.js:
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
function useNextUrlAdapter() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
return {
getSearchParams() {
return new URLSearchParams(searchParams.toString());
},
setSearchParams(next, mode) {
const qs = next.toString();
const url = qs ? `${pathname}?${qs}` : pathname;
if (mode === 'replace') router.replace(url);
else router.push(url);
},
};
}
function TodoSearchWithUrl() {
const urlAdapter = useNextUrlAdapter();
const { form, query, onSubmit } = kit(searchTodosContract).searchForm({
mapValuesToQueryParams: (values) => ({ query: values }),
urlSync: {
adapter: urlAdapter,
mode: 'replace', // or 'push'
writeStrategy: 'onSubmit', // or 'onChange'
},
});
return (
<form onSubmit={onSubmit}>
<input {...form.register('q')} />
<button>Search</button>
</form>
);
}Search Form with Schema-Based URL Coercion
Enable automatic type coercion of URL query params using the contract's query schema:
function TodoSearchWithSchemaCoercion() {
const urlAdapter = useNextUrlAdapter();
const { form, query, onSubmit } = kit(searchTodosContract).searchForm({
mapValuesToQueryParams: (values) => ({ query: values }),
urlSync: {
adapter: urlAdapter,
useSchema: true, // Enable schema-based parsing
onParseError: 'drop', // or 'throw' or custom handler
},
});
return (
<form onSubmit={onSubmit}>
<input {...form.register('q')} />
<button>Search</button>
</form>
);
}With schema-based parsing:
?completed=true→{ completed: true }(boolean, not string)?page=2→{ page: 2 }(number, not string)?tags=a&tags=b→{ tags: ['a', 'b'] }(array)- Invalid values are handled via
onParseErrorstrategy
Without schema-based parsing (default):
- All values remain as strings or arrays of strings
?completed=true→{ completed: 'true' }(string)
API
createFormQueryKit(options)
Creates a form query kit factory.
Options:
rq: React Query adapter fromcreateRQ(client)rhf: React Hook Form adapter function
Returns: A factory function that accepts a contract and returns mutation/edit form builders.
mutationForm(config)
Creates a mutation form for submitting data to an endpoint.
Config:
form: React Hook Form options (optional)mutation: React Query mutation options (optional)mapValuesToParams: Function to map form values to mutation params (default:{ body: values })onSuccess: Success callback (optional)mapErrorToForm: Error mapping function (optional)
Returns:
form: React Hook Form instancemutation: React Query mutation instanceonSubmit: Form submit handler
editForm(config)
Creates an edit form that queries data, populates the form, and submits mutations.
Config:
context: External context (e.g.,{ id }) passed to mapping functionsquery.contract: Contract for fetching dataquery.params: Function to map context to query paramsquery.options: React Query options (optional)mapQueryDataToValues: Function to map query data to form valuesmapValuesToMutationParams: Function to map form values to mutation params (default:{ body: values })form: React Hook Form options (optional)mutation: React Query mutation options (optional)resetStrategy: When to auto-reset form ('never'|'onFirstData'|'onEveryDataChange')mapErrorToForm: Error mapping function (optional)
Returns:
form: React Hook Form instancequery: React Query query instancemutation: React Query mutation instanceonSubmit: Form submit handlerresetFromQuery: Manual reset function
searchForm(config)
Creates a search form that syncs form values with query params and optionally with URL.
Config:
context: External context passed to mapping functions (optional)initialSearch: Initial search params (optional)form: React Hook Form options (optional)query: React Query options (optional)mapValuesToQueryParams: Required - Function to map form values to query contract paramsrunMode: When to run the query ('immediate'|'onSubmit', default:'immediate')submitUpdatesQuery: Whether submitting updates the query (default:true)select: Data transform function (optional)mapErrorToForm: Error mapping function (optional)urlSync: URL syncing options (optional):adapter: URL adapter for reading/writing query params (required)mode: History mode ('push'|'replace', default:'replace')writeStrategy: When to write to URL ('onSubmit'|'onChange', default:'onSubmit')keyPrefix: Prefix for URL param keys (optional)serialize: Custom serialization function (optional)parse: Custom parse function (optional)useSchema: Enable schema-based coercion of URL params (default:false)onParseError: Error handling for schema validation ('drop'|'throw'| custom function, default:'drop')includeKeys: Allowlist of keys to include in URL (optional)excludeKeys: Blocklist of keys to exclude from URL (optional)
Returns:
form: React Hook Form instancequery: React Query query instanceonSubmit: Form submit handlersearchParams: Current search params statesetSearchParams: Programmatic setter for search paramsresetSearch: Reset form and search params to initial stateurl: URL utilities (only whenurlSyncis configured):syncFromForm: Write current form values to URLsyncFromSearchParams: Write current search params to URLreadIntoForm: Read URL params into form
createBrowserUrlAdapter()
Creates a URL adapter for browser environments using the History API.
Returns: A UrlAdapter instance that reads/writes to window.location
License
MIT
