@gapu/formix is a powerful form management library for SolidJS. It provides a simple and flexible API for handling complex form state, validation, and submission.
To use @gapu/formix in your project, you need to install both @gapu/formix and Zod for schema validation.
# Using npm
npm install @gapu/formix zod
# Using yarn
yarn add @gapu/formix zod
# Using pnpm
pnpm add @gapu/formix zod
# Using bun
bun add @gapu/formix zod
Now you're ready to start using @gapu/formix in your SolidJS project!
The createForm
function is the entry point for creating and managing your form with the specified schema, initial state, and submission handler.
import { createForm } from '@gapu/formix';
import { z } from 'zod';
const formContext = createForm({
schema: z.object({
username: z.string().min(3),
email: z.string().email(),
age: z.number().min(18)
}),
initialState: {
username: '',
email: '',
age: 18,
},
onSubmit: async (state) => {
console.log('Form submitted:', state);
},
});
createForm
accepts an object with the following properties:
schema
: A Zod schema that defines the structure and validation rules for your form data.initialState
: The initial state of your form which matches your zod schema.onSubmit
: A function that will be called when the form is submitted successfully. It doesn't run if the form is invalid and it receives the validated form state as its argument.undoLimit
(optional, default = 500): The maximum number of undo steps to keep in history.createForm
returns a form context object with various methods and properties for managing your form:
initialState
: The initial state of the form.formSchema
: Zod schema used for the form validation.isFieldRequired
: A function to check if a field is required, given its path and optional variant.state
: A signal containing the current form state.setState
: A function to update the form state (use empty path: ""
to update entire form state).isValidating
: A function to check if the form is currently being validated by zod.isSubmitting
: A function to check if the form is currently being submitted.fieldMetas
: A signal containing metadata for all fields.setFieldMetas
: A function to update metadata for all fields.errors
: A signal containing any current validation errors.reset
: A function to reset the form to its initial state.submit
: A function to trigger form submission.undo
: A function to undo the last change in the form state.redo
: A function to redo the last undone change in the form state.canUndo
: A function that returns whether an undo operation is possible.canRedo
: A function that returns whether a redo operation is possible.wasModified
: A function that returns whether the form state has been modified from its initial state.setFieldMeta
: A function to update metadata for a specific field.const formContext = createForm({
schema: ...,
initialState: ...,
onSubmit: ...,
undoLimit: ... // (optional)
});
// Access current form state
const currentState = formContext.state();
// Update form state
formContext.setState("", newState);
// Check if form is currently submitting
const isSubmitting = formContext.isSubmitting();
// Get all current form errors
const formErrors = formContext.errors();
// Undo last change
if (formContext.canUndo()) {
formContext.undo();
}
// Check if a specific field is required
const isNameRequired = formContext.isFieldRequired('name');
The Form
component is a crucial part of this library. Its primary purpose is to serve as a context provider, making the form context available to all descendant components.
import { createForm, Form } from '@gapu/formix';
const formContext = createForm({ ... })
<Form context={formContext}>
{/* Your form fields and components go here */}
</Form>
The Form
component accepts two props:
context
: This is the form context object returned by the createForm
function. It contains all the form state, methods, and properties needed to manage your form.children
: This can be any valid JSX content. Typically, this will include your form fields, submit buttons, and any other UI elements that make up your form.The Form
component uses Solid's Context API to provide the form context to all its children.
It wraps its children in a <form> element, which handles the submit event.
When the form is submitted, it prevents the default form submission behavior and if the form is valid, calls the submit function from the provided context.
import { createForm, Form, useField } from '@gapu/formix';
import { z } from 'zod';
const MyForm = () => {
const formContext = createForm({
schema: z.object({
name: z.string().min(2),
email: z.string().email(),
}),
initialState: {
name: '',
email: '',
},
onSubmit: async (state) => {
console.log('Form submitted:', state);
},
});
return (
<Form context={formContext}>
<NameField />
<EmailField />
<button type="submit">Submit</button>
</Form>
);
};
const NameField = () => {
const field = useField<string>('name');
...
};
const EmailField = () => {
const field = useField<string>('email');
...
};
Form
component is essentially a context provider. It doesn't directly handle form state or validation itself.useField
) must be used within a Form
component to access the form context.Form
component renders a regular <form> element and handles the onSubmit event internally.useForm
, useField
or useArrayField
.By using the Form
component, you ensure that all parts of your form have access to the shared form context, allowing for seamless integration of form state, validation, and submission handling throughout your form's component tree.
The useForm
hook provides access to the entire form context within any component that is a child of a Form component.
The useForm
hook allows you to:
import { useForm } from '@gapu/formix';
const MyFormComponent = () => {
const form = useForm<MyFormState>();
...
};
useForm
returns the form context object with the following properties and methods:
initialState
: The initial state of the form.formSchema
: Zod schema used for the form validation.isFieldRequired
: A function to check if a field is required, given its path and optional variant.state
: A signal containing the current form state.setState
: A function to update the form state (use empty path: ""
to update entire form state).isValidating
: A function to check if the form is currently being validated by zod.isSubmitting
: A function to check if the form is currently being submitted.fieldMetas
: A signal containing metadata for all fields.setFieldMetas
: A function to update metadata for all fields.errors
: A signal containing any current validation errors.reset
: A function to reset the form to its initial state.submit
: A function to trigger form submission.undo
: A function to undo the last change in the form state.redo
: A function to redo the last undone change in the form state.canUndo
: A function that returns whether an undo operation is possible.canRedo
: A function that returns whether a redo operation is possible.wasModified
: A function that returns whether the form state has been modified from its initial state.setFieldMeta
: A function to update metadata for a specific field.import { useForm } from '@gapu/formix';
const FormSummary = () => {
const form = useForm();
return (
<div>
<h3>Form Summary</h3>
<p>Modified: {form.wasModified() ? 'Yes' : 'No'}</p>
<p>Can Undo: {form.canUndo() ? 'Yes' : 'No'}</p>
<p>Can Redo: {form.canRedo() ? 'Yes' : 'No'}</p>
<button onClick={form.reset} disabled={!form.wasModified()}>
Reset Form
</button>
<button onClick={form.undo} disabled={!form.canUndo()}>
Undo
</button>
<button onClick={form.redo} disabled={!form.canRedo()}>
Redo
</button>
<button onClick={form.submit}>Submit</button>
</div>
);
};
useForm
must be used within a component that is a child of a Form
component.By using the useForm
hook, you can create components that have full access to the form's state and functionality, enabling you to build complex form interactions and custom form controls.
The useField
hook provides a way to interact with individual form fields within a Form
context.
The useField
hook allows you to:
import { useField } from '@gapu/formix';
const MyFormField = () => {
const field = useField<string>('fieldName');
...
};
The useField
hook takes one parameter:
path
: A string representing the path to the field in your form state. For nested objects and arrays, use dot notation (e.g. 'user.name', 'contacts.1')useField
returns an object with the following properties and methods:
value
: A function that returns the current value of the field.setValue
: A function to update the value of the field.meta
: A function that returns the current metadata state of the field. The metadata includes:
touched
: A boolean indicating whether the field has been interacted with.dirty
: A boolean indicating whether the field's value has changed from its initial value.loading
: A boolean indicating whether the field is in a loading state.disabled
: A boolean indicating whether the field is currently disabled.readOnly
: A boolean indicating whether the field is in a read-only state.show
: A boolean indicating whether the field should be displayed.setMeta
: A function to update the metadata state of the field.errors
: A function that returns an array of current validation errors for the field.reset
: A function to reset the field to its initial value.wasModified
: A function that returns whether the field has been modified from its initial value.isRequired
: A function that returns whether the field is required based on the form schema.import { useField } from '@gapu/formix';
import { Index } from 'solid-js';
const EmailField = () => {
const field = useField<string>('email');
return (
<div>
<label>Email:</label>
<input
value={field.value()}
onInput={(e) => field.setValue(e.currentTarget.value)}
onFocus={() => field.setMeta(prev => ({ ...prev, touched: true }))}
disabled={field.meta().disabled}
/>
<Index each={hobbies.errors()}>
{(error) => (
<p class="error">{error().message}</p>
)}
</Index>
{field.wasModified() && <span>Field was modified</span>}
<button onClick={() => field.reset()}>Reset</button>
</div>
);
};
useField
must be used within a component that is a child of a Form
component.By using the useField
hook, you can create reusable, type-safe form field components that are automatically connected to your form's state and validation logic.
The useArrayField
hook is designed to handle array fields in your form. It provides an extended useField
API for manipulating array-type form fields.
import { useArrayField } from '@gapu/formix';
type ItemType = { ... }
const MyArrayField = () => {
const arrayField = useArrayField<ItemType>('arrayFieldName');
...
};
The useArrayField
hook takes one parameter:
path
: A string representing the path to the array field in your form state. For nested objects, use dot notation (e.g., 'user.hobbies').useArrayField
returns an object that includes all properties and methods from useField
, plus these additional array-specific methods:
push
: A function to add an item to the end of the array.remove
: A function to remove an item at a specific index.move
: A function to move an item from one index to another.insert
: A function to insert an item at a specific index.replace
: A function to replace an item at a specific index.empty
: A function to remove all items from the array.swap
: A function to swap the positions of two items in the array.import { useArrayField } from '@gapu/formix';
import { Index } from 'solid-js';
const HobbiesField = () => {
const hobbies = useArrayField<string>('hobbies');
return (
<div>
<h3>Hobbies</h3>
<Index each={hobbies.value()}>
{(hobby, index) => (
<div>
<input
value={hobby()}
onInput={(e) => hobbies.replace(index, e.currentTarget.value)}
/>
<button onClick={() => hobbies.remove(index)}>Remove</button>
</div>
)}
</Index>
<button onClick={() => hobbies.push('')}>Add Hobby</button>
<Index each={hobbies.errors()}>
{(error) => (
<p class="error">{error().message}</p>
)}
</Index>
</div>
);
};
useArrayField
must be used within a component that is a child of a Form
component.useField
, plus additional methods for array manipulation.push
, remove
, move
, etc.) automatically trigger form state updates and validation.setValue
, setMeta
, etc.) to manipulate the entire array at once.useArrayField
allows for complex array manipulations:
const hobbies = useArrayField<string>('hobbies');
// Move the first hobby to the end
hobbies.move(0, hobbies.value().length - 1);
// Swap the first two hobbies
hobbies.swap(0, 1);
// Insert a new hobby at the beginning
hobbies.insert(0, 'New Hobby');
// Replace all hobbies
hobbies.setValue(['Hobby1', 'Hobby2', 'Hobby3']);
By using the useArrayField
hook, you can easily create dynamic form sections that allow users to add, remove, and reorder items in an efficient manner.