react-on-rails
Version:
react-on-rails JavaScript for react_on_rails Ruby gem
137 lines • 6.99 kB
TypeScript
/**
* 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