Files
sendou.ink/app/hooks/useActionSubmit.ts
2026-09-05 21:34:59 +03:00

63 lines
2.0 KiB
TypeScript

import { type FetcherWithComponents, useFetcher } from "react-router";
import { holdRevalidationsDuring } from "~/features/chat/revalidation-scope";
import {
type ActionsOf,
type FieldsOf,
serializeFieldValue,
} from "~/utils/action-schemas";
import type { AnySchema } from "~/utils/schema";
interface UseActionSubmitOptions {
/** Route to submit to. Defaults to the current route. */
action?: string;
/** Defaults to "application/x-www-form-urlencoded". */
encType?: "application/x-www-form-urlencoded" | "application/json";
/** Fetcher to submit with, e.g. to share submitting state. Defaults to own fetcher. */
fetcher?: FetcherWithComponents<unknown>;
}
/**
* Programmatic `<ActionButton>`: submits an `_action` mutation type checked against the route's schema,
* e.g. `submit("DELETE_FRIEND", { friendshipId })`.
*/
export function useActionSubmit<TSchema extends AnySchema>(
_schema: TSchema,
opts?: UseActionSubmitOptions,
) {
const ownFetcher = useFetcher();
const fetcher = opts?.fetcher ?? ownFetcher;
const submit = <const TAction extends ActionsOf<TSchema>>(
action: TAction,
// biome-ignore lint/complexity/noBannedTypes: {} models "branch with no extra fields"
...rest: {} extends FieldsOf<TSchema, TAction>
? [fields?: FieldsOf<TSchema, TAction>]
: [fields: FieldsOf<TSchema, TAction>]
) => {
const fields = (rest[0] ?? {}) as Record<string, unknown>;
if (opts?.encType === "application/json") {
void holdRevalidationsDuring(() =>
fetcher.submit(
{ _action: action, ...fields } as Parameters<
typeof fetcher.submit
>[0],
{ method: "post", action: opts?.action, encType: "application/json" },
),
);
return;
}
const payload: Record<string, string> = { _action: action };
for (const [name, value] of Object.entries(fields)) {
if (value === undefined || value === null) continue;
payload[name] = serializeFieldValue(value);
}
void holdRevalidationsDuring(() =>
fetcher.submit(payload, { method: "post", action: opts?.action }),
);
};
return { submit, fetcher, state: fetcher.state };
}