@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 thecreateForm
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');
...
};
- The
Form
component is essentially a context provider. It doesn't directly handle form state or validation itself. - All components and hooks from @gapu/formix (such as
useField
) must be used within aForm
component to access the form context. - The
Form
component renders a regular <form> element and handles the onSubmit event internally. - Child components can access the form context using hooks like
useForm
,useField
oruseArrayField
.
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:
- Access and modify the entire form state
- Handle form-wide operations like submission, reset, and validation
- Perform undo and redo operations on the form state
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 aForm
component.- It provides access to the entire form context, allowing for form-wide operations and state management.
- The hook is generic, allowing you to specify the type of the form state for better type safety.
- It's particularly useful for creating components that need to interact with the overall form state or perform form-wide actions.
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:
- Access and modify the value of a specific field in your form
- Handle field-specific metadata (like touched, dirty, disabled states)
- Access field-specific validation errors
- Perform field-specific actions like resetting or checking if the field was modified
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 aForm
component.- This hook provides a comprehensive API for interacting with a single form field.
- It handles both the value of the field and its metadata (like disabled state).
- It provides access to field-specific validation errors.
- The hook is generic, allowing you to specify the type of the field value for better type safety.
- The meta function provides access to various metadata about the field's state, such as whether it has been touched, is dirty, or is disabled.
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.
- Access and modify an array field in your form
- Perform array-specific operations like pushing, removing, moving, and swapping items
- Handle field-specific metadata and validation errors for the entire array
- Perform field-specific actions like resetting or checking if the array was modified
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 aForm
component.- It provides all the functionality of
useField
, plus additional methods for array manipulation. - The hook is generic, allowing you to specify the type of the array items for better type safety.
- Array operations (
push
,remove
,move
, etc.) automatically trigger form state updates and validation. - You can use the base field methods (
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.