Skip to content
Form, Please

Query string persistence

This example stores the editable form draft in the URL draft parameter. It uses the optional persistence middleware while React Hook Form continues to own live values, validation, defaults, and form metadata.

Try the workflow

  1. Edit Name or Role and watch the URL after 250 ms.
  2. Select Save now to flush without waiting.
  3. Reload the page to restore the query string draft.
  4. Select Clear saved draft to remove the parameter without changing the current fields.

Query string draft

Edit either field. The URL draft parameter updates after 250 ms.

Restore: idle. Save: idle.

The adapter uses history: "replace" and shallow: true. Autosave does not add a browser history entry for each edit and does not start a page navigation.

What this example shows

  • createPersistenceMiddleware as an optional form-please/persistence feature;
  • usePersistence(form, feature) for automatic restore after mount;
  • the hook's reactive snapshot for restore and save state;
  • a nuqs string state adapted to the keyed asynchronous transport.

Copy the source

"use client"
 
import {
	,
	,
} from "form-please/persistence"
import {  } from "form-please/preset-native"
import { ,  } from "nuqs"
import { ,  } from "react"
import {  } from "zod"
 
import {  } from "./persistence-nuqs.js"
 
const  = .({
	: .().(1, "Enter a name"),
	: .(),
})
 
const  = .(, {
	: [
		{ : "text", : "field", : "Name", : "name" },
		{ : "text", : "field", : "Role", : "role" },
	],
})
 
const  = .({
	: "replace",
	: true,
})
 
export function () {
	const [, ] = ("draft", )
	const  = ()
	. = 
 
	const [] = (() =>
		({
			: ({
				: () => new (..).("draft"),
				: () => .(),
			}),
			: "profile",
			: 250,
			: 1,
		}),
	)
	const  = .(, {
		: { : "Ada Lovelace", : "Programmer" },
		: [],
	})
	const  = (, )
	const {  } = 
	const  =
		. === "conflict" || . === "failed"
 
	return (
		<
			="Query string persistence preview"
			="form-please-complex form-please-lab"
		>
			< ="form-please-lab__kicker">Query string draft</>
			< ="form-please-lab__summary">
				Edit either field. The URL draft parameter updates after 250 ms.
			</>
			<. ="form-please-lab__form" ={}>
				< ="form-please-lab__actions">
					<
						={() => void .().(() => )}
						="button"
					>
						Save now
					</>
					<
						={() => void .().(() => )}
						="button"
					>
						Clear saved draft
					</>
					{ && (
						< ={() => .()} ="button">
							Keep current form
						</>
					)}
				</>
				< ="polite">
					Restore: {.}. Save: {..}.
				</>
			</.>
		</>
	)
}
import type { ,  } from "form-please/persistence"
 
export type  = <{
	(): string | null
	(: string | null): unknown | <unknown>
}>
 
/** Adapts a nuqs string state to the Form Please persistence transport. */
export function (
	: ,
):  {
	return {
		async () {
			const  = .()
			if ( === null) return 
			return .() as 
		},
		async () {
			await .(null)
		},
		async (, ) {
			await .(.())
		},
	}
}

Read Form persistence for conflicts, migrations, codecs, storage failures, and lifecycle boundaries.