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
isFormDirty
helper. - 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
true
if 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
isEdit?: () => boolean | undefined
id?: () => PropertyType<FormModel, 'id'> | undefined
onStartLoading?: () => void
onStopLoading?: () => void
onClose?: () => void
onCreated?: (entity: Plain<FormModel>) => void
onUpdated?: () => void
onDeleted?: () => void
}
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:
isEdit
- Type:
() => boolean | undefined
- Optional:
true
- Details: Indicates if the form is in edit mode (updating an existing record) versus create mode.
- Type:
id
- Type:
() => PropertyType<FormModel, 'id'> | undefined
- Optional:
true
- Details: The identifier of the model being edited. This is required when
isEdit
is set totrue
.
- 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:
onClose
- Type:
() => void | undefined
- Details: Callback to run when the form should be closed (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:
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,
isEdit,
api,
id,
forceValues,
populateForm,
attachTo,
onStartLoading,
onStopLoading,
onClose,
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>
} { ... }
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 (
isEdit
istrue
), it fetches the latest details from the API. - Otherwise, it resets the form to the default state provided by
defaultData
.
- In edit mode (
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
,onClose
) are executed, and relations defined inattachTo
are updated.
remove()
- Type:
() => Promise<void>
- Details: Deletes the current record:
- Prompts the user with a confirmation dialog before deletion.
- On successful deletion, the
onDeleted
callback is executed.
isDirty
- Type:
Ref<boolean>
- Details: A computed reference that returns
true
if the current form data differs from the original (initial) form data.
Internal Workings
The composable also defines several helper functions and lifecycle hooks to manage its behavior:
Lifecycle Hooks
onBeforeMount
- Adds a
beforeunload
event listener to warn users of unsaved changes (if redirect protection is enabled). - Calls
reset()
to initialize the form state.
- Adds a
onUnmounted
- Removes the
beforeunload
event listener.
- Removes the
onBeforeRouteLeave
- Uses the
beforeRedirect
function 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
store
method. It merges any forced values with the current form data, handles validation errors by populatingformErrors
, and calls theonCreated
callback.
update()
- Returns: A promise that resolves to the updated entity.
- Details: Sends a PUT request using the API’s
update
method. Similar tocreate()
, it merges forced values and handles validation errors, calling theonUpdated
callback when successful.
attachToAll(id: PropertyType<FormModel, 'id'>)
- Details: If the
attachTo
option is provided, this function iterates over the related models to attach, calling the API’supdateRelation
method for each.