npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@contract-kit/react

v0.1.1

Published

React utilities for combining React Query and React Hook Form with Contract Kit

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-form

TypeScript 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 onParseError strategy

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 from createRQ(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 instance
  • mutation: React Query mutation instance
  • onSubmit: 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 functions
  • query.contract: Contract for fetching data
  • query.params: Function to map context to query params
  • query.options: React Query options (optional)
  • mapQueryDataToValues: Function to map query data to form values
  • mapValuesToMutationParams: 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 instance
  • query: React Query query instance
  • mutation: React Query mutation instance
  • onSubmit: Form submit handler
  • resetFromQuery: 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 params
  • runMode: 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 instance
  • query: React Query query instance
  • onSubmit: Form submit handler
  • searchParams: Current search params state
  • setSearchParams: Programmatic setter for search params
  • resetSearch: Reset form and search params to initial state
  • url: URL utilities (only when urlSync is configured):
    • syncFromForm: Write current form values to URL
    • syncFromSearchParams: Write current search params to URL
    • readIntoForm: 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