UNPKG

react-on-rails

Version:

react-on-rails JavaScript for react_on_rails Ruby gem

137 lines 6.99 kB
/** * useRailsForm — a small, Inertia `useForm`-style hook for submitting React * forms to plain Rails controller actions. * * The hook keeps Rails as the mutation layer: it wires up `fetch`, attaches the * CSRF token from the standard Rails `<meta name="csrf-token">` tag (via the * existing `authenticityToken` utility), sends/receives JSON, and maps the * blessed 422 validation-error shape — `{ errors: { field: ["message"] } }` — * onto per-field client state. The matching server side is the opt-in * `ReactOnRails::Controller::FormResponders#render_model_errors` concern in the * react_on_rails gem, but the hook works against any endpoint that returns the * documented shape. * * v1 scope (https://github.com/shakacode/react_on_rails/issues/3872): * submit verbs, `data`/`setData`, `errors`, `processing`, CSRF auto-attach, and * 422 error mapping. Deferred to a follow-up: `transform`, * `recentlySuccessful`, and file-upload `progress` (which requires an * XMLHttpRequest or duplex-stream transport; v1 is fetch-only). * * Success/redirect handling is intentionally minimal and forward-compatible * with the client-routing work in issue #3873: the hook never navigates on its * own. It surfaces safe JSON `redirect_to` hints through `onSuccess` / the * resolved submit result so the app — or a future router integration — decides * what to do. */ /** Per-field validation errors: `{ field: ["message", ...] }`. */ export type RailsFormErrors = Record<string, string[]>; export type RailsFormMethod = 'post' | 'put' | 'patch' | 'delete'; export interface RailsFormSuccessResult { ok: true; /** Parsed JSON response body, or `null` when the body was empty or not JSON. */ responseData: unknown; /** * Redirect target when the server replied with a safe JSON * `redirect_to`/`redirectTo` hint. Browser redirect following is disabled for * CSRF-bearing submissions; a defensively filtered redirected Response URL is * still accepted if a custom fetch implementation returns one. * Hints are accepted only when they resolve to the current origin over HTTP(S); * non-HTTP schemes such as `javascript:` are ignored. * The hook never navigates — pass this to your router or `window.location`. * Designed to compose with the client-routing integration in issue #3873. */ redirectTo: string | null; response: Response; } export interface RailsFormValidationErrorResult { ok: false; /** Per-field errors mapped from the 422 response body. */ errors: RailsFormErrors; response: Response; } export interface RailsFormStaleResult { ok: false; /** * True when this submit was superseded by a newer submit before it settled. * Stale submissions do not update form state, run submit callbacks, or reject * stale caller `.catch()` handlers after the newer submit has won. */ stale: true; response?: Response; error?: unknown; } export type RailsFormSubmitResult = RailsFormSuccessResult | RailsFormValidationErrorResult | RailsFormStaleResult; /** Thrown (as a promise rejection) for non-2xx responses other than a mappable 422. */ export declare class RailsFormRequestError extends Error { /** The response, with its body stream unread — `.json()`/`.text()` work. */ readonly response: Response; /** * Parsed JSON body when the hook already read it (a 422 whose body didn't * match the documented errors shape); `undefined` otherwise. */ readonly responseBody: unknown; constructor(response: Response, responseBody?: unknown); } export interface RailsFormSubmitOptions { /** Extra request headers. JSON and CSRF headers are always applied on top. */ headers?: Record<string, string>; /** * Called after a 2xx response. If this callback throws, the exception * propagates as the submit promise rejection after form state has settled. */ onSuccess?: (result: RailsFormSuccessResult) => void; /** Called after a 422 response whose body matched the documented errors shape. */ onError?: (errors: RailsFormErrors) => void; } export interface UseRailsForm<TData extends object> { /** Current form data. */ data: TData; /** Set a single field, merge a partial object, or apply an updater function. */ setData: { <K extends keyof TData>(key: K, value: TData[K]): void; (valuesOrUpdater: Partial<TData> | ((previousData: TData) => TData)): void; }; /** Per-field validation errors from the last 422 response (or `setError`). */ errors: RailsFormErrors; hasErrors: boolean; /** True while a submission is in flight. */ processing: boolean; /** True once the most recent submission succeeded. Reset when a new one starts. */ wasSuccessful: boolean; /** Submit with an explicit HTTP method. */ submit: (method: RailsFormMethod, url: string, options?: RailsFormSubmitOptions) => Promise<RailsFormSubmitResult>; post: (url: string, options?: RailsFormSubmitOptions) => Promise<RailsFormSubmitResult>; put: (url: string, options?: RailsFormSubmitOptions) => Promise<RailsFormSubmitResult>; patch: (url: string, options?: RailsFormSubmitOptions) => Promise<RailsFormSubmitResult>; /** Named `delete` on the hook object; `delete` is reserved in some contexts. */ delete: (url: string, options?: RailsFormSubmitOptions) => Promise<RailsFormSubmitResult>; /** * Reset all data (no args) or the given fields to their initial values. * Clears matching errors and `wasSuccessful`. "Initial values" are the * `initialData` captured on first render (Inertia `useForm` semantics) — * later prop changes are not tracked; remount the component to re-seed. */ reset: (...fields: Extract<keyof TData, string>[]) => void; /** Clear all errors (no args) or the errors for the given fields. */ clearErrors: (...fields: string[]) => void; /** Manually set the errors for one field (e.g. client-side pre-checks). */ setError: (field: string, messages: string | string[]) => void; } /** * React hook for submitting form data to a Rails controller action. * * ```tsx * const form = useRailsForm({ name: '', email: '' }); * // <input value={form.data.name} onChange={(e) => form.setData('name', e.target.value)} /> * // {form.errors.name?.[0]} * // <form onSubmit={(e) => { e.preventDefault(); void form.post('/contacts'); }}> * ``` * * Submissions send `Content-Type: application/json` / `Accept: application/json` * with the CSRF token from the Rails csrf-token meta tag. A 422 response with a * `{ errors: { field: ["message"] } }` body (the shape rendered by the * `render_model_errors` controller concern) populates `errors`; other non-2xx * responses reject with `RailsFormRequestError`. */ export declare function useRailsForm<TData extends object>(initialData: TData): UseRailsForm<TData>; //# sourceMappingURL=useRailsForm.d.ts.map