Custom Adapters

Binding kvant to your own key-value interfaces in React.

When the built-in interfaces aren't enough (an external store, an in-memory map, a query-string dialect, a native bridge), you can write your own adapter for any key-value interface.

The adapter contract

An adapter is a hook receiving the bound keys and resolved options, and returning an interface object:

interface KvantReactAdapterInterface<T> {
  /** Unique adapter identifier, used to namespace sync channels. */
  readonly key: string
  /** Current raw values for the adapter's keys. Must be referentially stable between changes. */
  readonly snapshot: Readonly<Record<string, T | undefined>>
  /** Writes raw values to the underlying storage. */
  readonly update: (values: Record<string, unknown>) => void
}

type KvantReactAdapter<T, Options> = (
  keys: string[],
  options: Partial<Options>,
) => KvantReactAdapterInterface<T>

To have a better idea of how to implement an adapter, check out the source code of the built-in adapters:

InterfaceAdapterSource
Next.js (app router) → Search Paramskvantjs/nextuseSearchParamsKvantAdapterGitHub
Next.js (pages router) → Router Querykvantjs/next/pagesuseRouterQueryKvantAdapterGitHub
React Router → Search Paramskvantjs/react-routeruseSearchParamsKvantAdapterGitHub

Framework-agnostic adapters

If you want to share an adapter across framework families (React, Vue), you can implement a low-level, framework-agnostic adapter:

interface KvantAdapterInterface<T> {
  /** Unique adapter identifier, used to namespace sync channels. */
  readonly key: string
  /**
   * Subscribes to snapshot changes.
   * @returns An unsubscribe function.
   */
  readonly subscribe: (callback: () => void) => () => void
  /** Returns the current raw values for the adapter's keys. */
  readonly getSnapshot: () => Record<string, T | undefined>
  /**
   * Returns the raw values as rendered on the server (SSR).
   * Used as the server snapshot during hydration,
   * the real client values are applied right after hydration.
   *
   * @default getSnapshot
   */
  readonly getServerSnapshot?: () => Record<string, T | undefined>
  /** Writes raw values to the underlying storage. */
  readonly update: (values: Record<string, unknown>) => void
  /**
   * Register necessary effects (listeners, subscriptions, etc.)
   * @returns A cleanup function to dispose of the effects.
   */
  readonly effects?: () => (() => void) | void
}

type KvantAdapter<T, Options> = (
  keys: string[],
  options: Partial<Options>,
) => KvantAdapterInterface<T>

To have a better idea of how to implement a framework-agnostic adapter, check out the source code of the built-in adapters:

InterfaceAdapterSource
Search ParamskvantjsuseSearchParamsKvantAdapterGitHub
CookieskvantjsuseCookiesKvantAdapterGitHub
Web StoragekvantjsuseStorageKvantAdapterGitHub

On this page