Headless Core
Quick Start
Get started with headless Goodie Forms core.
Remember!
@goodie-forms/core exists mainly as a headless virtual form state tracker and data manager.
If you don't need low-level building blocks, you can always check on the conviniently wrapped packages such as @goodie-forms/react or @goodie-forms/vue.Install Dependencies
- Install Goodie Forms
pnpm i @goodie-forms/core
npm i @goodie-forms/core --save-dependency
yarn add @goodie-forms/core
- Install a Validation Library (totally optional) See supported libs
pnpm i zod # Or any other validation lib
npm i zod --save-dependency # Or any other validation lib
yarn add zod # Or any other validation lib
Build your first DOM
<form id="userform">
<input id="firstname" type="text" />
<input id="lastname" type="text" />
<input id="age" type="number" />
<button type="submit">Submit</button>
</form>
Build your first Form
import { FormController } from "@goodie-forms/core";
import type { FieldPath } from "@goodie-forms/core";
import z from "zod"; // <-- Can be any StandardSchemaV1 compliant lib!
// An examplar schema with 3 values
const UserSchema = z.object({
firstname: z.string().nonempty(),
lastname: z.string().nonempty(),
age: z.number().int(),
});
const formController = new FormController({
// ^? FormController<{ firstname: string; lastname: string; age: number; }>
validationSchema: UserSchema,
initialData: {
firstname: "",
lastname: "",
},
});
Register your first Fields
// Register firstname field, and bind a DOM node
// Remember, the control is yours with this headless core lib!
const firstnamePath = formController.path.of("firstname");
// ^ IntelliSense will suggest the paths
const firstnameField = formController.registerField(firstnamePath);
const firstnameEl = document.getElementById("fistname") as HTMLInputElement;
firstnameEl.onchange = (e) =>
firstnameField.setValue((e.target as HTMLInputElement).value);
firstnameField.bindElement(firstnameEl);
// Maybe even build a helper to encapsulate some logic!
function registerWithElement<
TOutput extends object,
TPath extends FieldPath.Segments,
TElement extends HTMLElement
>(
formController: FormController<TOutput>,
path: TPath,
el: TElement,
valueGetter: (el: TElement) => FieldPath.Resolve<TOutput, TPath>,
) {
const field = formController.registerField(path);
el.onchange = (e) => field.setValue(valueGetter(e.target as TElement));
field.bindElement(el);
return field;
}
const lastnameField = registerWithElement(
formController,
formController.path.of("lastname"),
document.getElementById("lastname") as HTMLInputElement,
(el) => el.value,
);
const ageField = registerWithElement(
formController,
formController.path.of("age"),
document.getElementById("age") as HTMLInputElement,
(el) => el.valueAsNumber,
);
Attach Submit Handler
const formEl = document.getElementById("form") as HTMLFormElement;
formEl.onsubmit = formController.createSubmitHandler(
// Success callback
async (data, event) => {
// ^? { firstname: string; lastname: string; age: number; }
event.preventDefault();
//^? SubmitEvent
console.log("Do whatever, with", data);
await maybeSendToARemoteAPI(data);
orConsumeSyncLocally(data);
},
// Error callback, with issues arised
async (issues) => {
// ^? StandardSchemaV1.Issue[]
console.log("Wops, cannot submit. These issues raised:", issues);
},
);