Appearance
useForm
The useForm composable provides a comprehensive solution for managing form state in your Vue application. It handles creating, updating, and deleting models via a provided API, tracks loading and error states, and even protects against unwanted redirects when there are unsaved changes.
Note: This composable makes use of several Vue composition API features as well as PrimeVue's confirmation dialog.
Overview
The useForm composable is designed to simplify common form-handling tasks:
- Dirty Checking: It determines if the form has been modified using the
isFormDirtyhelper. - Loading State & Errors: Manages loading indicators and form error messages.
- CRUD Operations: Supports create, update, and delete operations using your provided API.
- Redirect Protection: Warns users if they attempt to navigate away from the page with unsaved changes.
- Relation Attachments: Optionally attaches related models after creation or update.
isFormDirty
typescript
export function isFormDirty(
original: Record<string, any>,
current: Record<string, any>
): boolean {
for (const key in original) {
if (original[key] !== current[key]) {
return true
}
}
return false
}The isFormDirty function compares two objects—the original form data and the current form data—to determine if any field has been modified.
Parameters
original- Type:
Record<string, any> - Description: The initial state of the form data.
- Type:
current- Type:
Record<string, any> - Description: The current state of the form data.
- Type:
Returns
- Type:
boolean - Description: Returns
trueif at least one value in the form has changed; otherwise, returnsfalse.
FormOptions Interface
typescript
export interface FormOptions<
FormModel extends Model,
FormApi extends Api<FormModel>,
FormData extends Parameters<FormApi['store']>[0] | Parameters<FormApi['update']>[1],
> {
redirectProtection?: () => boolean | undefined
defaultData: () => FormData
api: () => FormApi
forceValues?: () => Record<string, any> | undefined
populateForm?: (data: Partial<FormData> | Plain<FormModel>) => void
attachTo?: () =>
| Record<string, { method: 'associate' | 'syncWithoutDetaching'; id: string | number }>
| undefined
id?: Ref<PropertyType<FormModel, 'id'> | undefined | null>
onStartLoading?: () => void
onStopLoading?: () => void
onSubmit?: () => void
onCreated?: (entity: Plain<FormModel>) => void
onUpdated?: () => void
onDeleted?: () => void
formatOnCreate?: (data: FormData) => Parameters<FormApi['store']>[0]
formatOnUpdate?: (data: FormData) => Parameters<FormApi['update']>[1]
}The FormOptions interface defines the configuration for the useForm composable.
Options
redirectProtection- Type:
() => boolean | undefined - Default:
() => true - Optional:
true - Details: When enabled, the composable will warn users about unsaved changes if they attempt to navigate away.
- Type:
defaultData- Type:
() => FormData - Details: Function that returns the default form data used to initialize the form.
- Type:
api- Type:
() => FormApi - Details: An API instance (extending the default CRUD API) used to communicate with your backend.
- Type:
forceValues- Type:
() => Record<string, any> | undefined - Optional:
true - Details: Additional values to merge with the form data on submit.
- Type:
populateForm- Type:
(data: Partial<FormData> | Plain<FormModel>) => void - Details: A callback used to populate the form with data (typically when editing). If not provided, a default implementation will clone and update the form state.
- Type:
attachTo- Type:
() => Record<string, { method: 'associate' | 'syncWithoutDetaching'; id: string | number }> | undefined - Optional:
true - Details: Defines any relations to attach after a successful create or update operation. The key represents the relation name, and the value includes the relation method and the related model's identifier.
- Type:
id- Type:
Ref<PropertyType<FormModel, 'id'> | undefined | null> - Optional:
true - Details: A reactive reference to the identifier of the model being edited. If
idis set (notundefinedornull), the form will be in edit mode and will load the entity data when initialized. UsetoRef(props, 'id')to create a reactive reference from a prop.
- Type:
onStartLoading- Type:
() => void | undefined - Details: Callback fired before a loading operation begins.
- Type:
onStopLoading- Type:
() => void | undefined - Details: Callback fired after a loading operation ends.
- Type:
onSubmit- Type:
() => void | undefined - Details: Callback to run when the form has been submitted (e.g., after a successful submit or delete).
- Type:
onCreated- Type:
(entity: Plain<FormModel>) => void | undefined - Details: Callback invoked after a successful create operation.
- Type:
onUpdated- Type:
() => void | undefined - Details: Callback invoked after a successful update operation.
- Type:
onDeleted- Type:
() => void | undefined - Details: Callback invoked after a successful deletion.
- Type:
formatOnCreate- Type:
(data: FormData) => Parameters<FormApi['store']>[0] - Optional:
true - Details: A function that transforms the form data before it is sent to the API's
storemethod. This is useful for custom data formatting, such as removing empty fields or transforming values. If not provided, the form data is sent as-is.
- Type:
formatOnUpdate- Type:
(data: FormData) => Parameters<FormApi['update']>[1] - Optional:
true - Details: A function that transforms the form data before it is sent to the API's
updatemethod. This is useful for custom data formatting, such as removing empty password fields when updating a user. If not provided, the form data is sent as-is.
- Type:
useForm Function
typescript
export function useForm<
FormApi extends Api<FormModel>,
FormModel extends Model,
FormData extends Parameters<FormApi['store']>[0] | Parameters<FormApi['update']>[1],
>({
redirectProtection = true,
defaultData,
api,
id,
forceValues,
populateForm,
attachTo,
onStartLoading,
onStopLoading,
onSubmit,
onCreated,
onUpdated,
onDeleted,
}: FormOptions<FormModel, FormApi, FormData>): {
formData: Ref<FormData>
loading: Ref<boolean>
formErrors: Ref<Record<string, string>>
reset: () => void
submit: () => Promise<void>
remove: () => Promise<void>
isDirty: Ref<boolean>
isEdit: Ref<boolean>
} { ... }The useForm composable encapsulates the state and logic for handling forms. It provides reactive references for the form data, loading state, and error messages, along with functions to reset, submit, and remove records.
Returned Properties and Methods
formData
- Type:
Ref<FormData> - Details: Holds the current state of the form. This reactive reference can be used directly in your template for two-way binding.
loading
- Type:
Ref<boolean> - Details: Indicates whether a request (fetch, submit, or delete) is currently in progress.
formErrors
- Type:
Ref<Record<string, string>> - Details: An object that maps field names to validation error messages.
reset()
- Type:
() => void - Details: Resets the form state:
- In edit mode (when
idis set), it fetches the latest details from the API. - Otherwise, it resets the form to the default state provided by
defaultData.
- In edit mode (when
submit()
- Type:
() => Promise<void> - Details: Submits the form data:
- If in edit mode, it sends an update request.
- Otherwise, it creates a new record.
- After a successful operation, any defined callbacks (
onCreated,onUpdated,onSubmit) are executed, and relations defined inattachToare updated.
remove()
- Type:
() => Promise<void> - Details: Deletes the current record:
- Prompts the user with a confirmation dialog before deletion.
- On successful deletion, the
onDeletedcallback is executed.
isDirty
- Type:
Ref<boolean> - Details: A computed reference that returns
trueif the current form data differs from the original (initial) form data.
isEdit
- Type:
Ref<boolean> - Details: A computed reference that returns
trueif the form is in edit mode (i.e., whenidis set and notundefinedornull). This is automatically computed based on whether theidoption was provided.
Internal Workings
The composable also defines several helper functions and lifecycle hooks to manage its behavior:
Lifecycle Hooks
onBeforeMount- Adds a
beforeunloadevent listener to warn users of unsaved changes (if redirect protection is enabled). - Calls
reset()to initialize the form state.
- Adds a
onUnmounted- Removes the
beforeunloadevent listener.
- Removes the
onBeforeRouteUpdate- Uses the
beforeRedirectfunction to display a confirmation dialog if the form is dirty and redirect protection is enabled.
- Uses the
Internal Functions
beforeRedirect(next: () => void)
- Details: Checks if the form has unsaved changes before allowing navigation. If changes exist, it uses a confirmation dialog (via PrimeVue’s
useConfirm) to either allow or cancel the route change.
getDetails()
- Returns: A promise that resolves to the current model data.
- Details: Fetches the details for the record from the API when in edit mode, updating the loading state and calling any loading callbacks.
create()
- Returns: A promise that resolves to the newly created entity.
- Details: Sends a POST request using the API's
storemethod. It merges any forced values with the current form data, applies theformatOnCreatefunction if provided, handles validation errors by populatingformErrors, and calls theonCreatedcallback.
update()
- Returns: A promise that resolves to the updated entity.
- Details: Sends a PUT request using the API's
updatemethod. Similar tocreate(), it merges forced values, applies theformatOnUpdatefunction if provided, handles validation errors, and calls theonUpdatedcallback when successful.
attachToAll(id: PropertyType<FormModel, 'id'>)
- Details: If the
attachTooption is provided, this function iterates over the related models to attach, calling the API’supdateRelationmethod for each.