alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
138 lines (124 loc) • 3.62 kB
text/typescript
import { type TObject, TypeBoxError } from "alepha";
import { useAlepha } from "alepha/react";
import { useEffect, useState } from "react";
import type { FormModel } from "../services/FormModel.ts";
export interface UseFormStateReturn {
loading: boolean;
dirty: boolean;
values?: Record<string, any>;
error?: Error;
}
export const useFormState = <
T extends TObject,
Keys extends keyof UseFormStateReturn,
>(
target: FormModel<T> | { form: FormModel<T>; path: string },
_events: Keys[] = ["loading", "dirty", "error"] as Keys[],
): Pick<UseFormStateReturn, Keys> => {
const alepha = useAlepha();
const events = _events as string[];
const [dirty, setDirty] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
const [values, setValues] = useState<Record<string, any> | undefined>(
undefined,
);
const form = "form" in target ? target.form : target;
const path = "path" in target ? target.path : undefined;
const hasValues = events.includes("values");
const hasErrors = events.includes("error");
const hasDirty = events.includes("dirty");
const hasLoading = events.includes("loading");
useEffect(() => {
const listeners: Function[] = [];
if (hasErrors || hasValues || hasDirty) {
listeners.push(
alepha.events.on("form:change", (event) => {
if (event.id === form.id) {
if (!path || event.path === path) {
// `initial: true` marks a programmatic reset (e.g. parent
// re-rendering with fresh data via `setInitialValues`). Treat
// it like a reset: clear dirty, don't mark dirty.
if (hasDirty) {
if (event.initial) {
setDirty(false);
} else {
setDirty(true);
}
}
if (hasErrors) {
setError(undefined);
}
}
if (hasValues) {
setValues(form.currentValues);
}
}
}),
);
}
if (hasLoading) {
listeners.push(
alepha.events.on("form:submit:begin", (event) => {
if (event.id === form.id) {
setLoading(true);
}
}),
alepha.events.on("form:submit:end", (event) => {
if (event.id === form.id) {
setLoading(false);
}
}),
);
}
if (hasValues || hasDirty) {
listeners.push(
alepha.events.on("form:submit:success", (event) => {
if (event.id === form.id) {
if (hasValues) {
setValues(event.values);
}
if (hasDirty) {
setDirty(false);
}
}
}),
);
}
if (hasDirty) {
listeners.push(
alepha.events.on("form:reset", (event) => {
if (event.id === form.id) {
setDirty(false);
}
}),
);
}
if (hasErrors) {
listeners.push(
alepha.events.on("form:submit:error", (event) => {
if (event.id === form.id) {
if (
!path ||
(event.error instanceof TypeBoxError &&
event.error.value.path === path)
) {
setError(event.error);
}
}
}),
);
}
return () => {
for (const unsub of listeners) {
unsub();
}
};
}, []);
return {
dirty,
loading,
error,
values,
} as Pick<UseFormStateReturn, Keys>;
};