Skip to content
This repository was archived by the owner on Apr 9, 2025. It is now read-only.

Commit 1dd6662

Browse files
author
Daniel Requejo
committed
📜 Base handler, component docs & code alignment
1 parent 54b67b6 commit 1dd6662

File tree

8 files changed

+252
-66
lines changed

8 files changed

+252
-66
lines changed

docs/.vitepress/config.cts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export default defineConfig({
5151
{
5252
text: 'API Reference', items: [
5353
{
54-
text: 'useFormHandler', link: '/api/use-form-handler/index', items: [
54+
text: 'useFormHandler', link: '/api/use-form-handler/',
55+
items: [
5556
{ text: 'clearError', link: '/api/use-form-handler/clear-error' },
5657
{ text: 'clearField', link: '/api/use-form-handler/clear-field' },
5758
{ text: 'formState', link: '/api/use-form-handler/form-state' },

docs/api/form-handler.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,34 @@
1-
# \<FormHandler/>
1+
# \<FormHandler/>: <font size="3">Component</font>
2+
3+
The `FormHandler` component is an utility for people that are still using the options API or, that for some reason want to do the handling on the template side.
4+
5+
::: tip
6+
It is highly recommend that you use the `useFormHandler` composable if possible, since it is the intended way of making use of this package and gives you more control over the form.
7+
:::
8+
## How it works
9+
10+
You pass the same props as you'd be passing to the composable but to a component on the template, and the component will enable you a [scoped slot](https://vuejs.org/guide/components/slots.html#scoped-slots) with the return of `useFormHandler`.
11+
12+
As you can imagine, the `FormHandler` composable and `useFormHandler` share the same API but differ in the usage.
13+
14+
## Example:
15+
16+
```vue
17+
<template @submit.prevent="handleSubmit(successFn)">
18+
<FormHandler>
19+
<template v-slot="{register, handleSubmit}">
20+
<form @submit.prevent="handleSubmit(successFn)">
21+
<input v-bind="register('firstName')">
22+
<input v-bind="register('lastName')">
23+
</form>
24+
</template>
25+
</FormHandler>
26+
</template>
27+
<script setup lang="ts">
28+
import { FormHandler } from 'vue-form-handler'
29+
30+
const successFn = (form:Record<string,any>) => {
31+
console.log({form})
32+
}
33+
</script>
34+
```

docs/api/use-form-handler/index.md

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,160 @@
1-
# useFormHandler
1+
# useFormHandler: <font size=3>FormHandler</font>
2+
3+
`useFormHandler` is our composable to handle forms, it takes one object as **optional** argument and returns various objects and methods that will allow us to handle our forms.
4+
5+
## Props
6+
7+
### initialValues: <font size=2>Record<string,any></font>
8+
9+
::: warning
10+
Changing the initialValues will trigger a form reset, this means the formState and the values will return to its initial state.
11+
:::
12+
13+
Values which we use to initialize the form, this is useful when we get an initial value state for the form from an external call.
14+
15+
#### Example
16+
```vue
17+
<template>
18+
<input v-bind="register('firstName')"> // Should initially be 'John'
19+
<input v-bind="register('lastName')"> // Should initially be 'Doe'
20+
<pre>{{values}}</pre> // Should show the initialized values
21+
</template>
22+
<script setup lang="ts">
23+
import { useFormHandler } from 'vue-form-handler'
24+
25+
const { register, values } = useFormHandler({
26+
initialValues: {
27+
firstName: 'John',
28+
lastName: 'Doe'
29+
}
30+
})
31+
</script>
32+
```
33+
34+
### interceptor: <font size=2>Interceptor</font>
35+
36+
Function that allows us to intercept any value assignment, accept or deny it and perform operations before and after the assignment happens.
37+
38+
#### Params
39+
40+
The interceptor will be called passing an object as a parameter with the following:
41+
42+
| attribute | type | description |
43+
|-----------|--------|--------------------------------------------|
44+
| name | string | Name of the field that is about to be set |
45+
| value | any | Value of the field that is about to be set |
46+
| clearError | ClearError | [API - clearError](/api/use-form-handler/clear-error) |
47+
| clearField | ClearField | [API - clearField](/api/use-form-handler/clear-field) |
48+
| formState | FormState | [API - formState](/api/use-form-handler/form-state) |
49+
| modifiedValues | ModifiedValues | [API - modifiedValues](/api/use-form-handler/modified-values) |
50+
| resetField | ResetField | [API - resetField](/api/use-form-handler/reset-field) |
51+
| resetForm | ResetForm | [API - resetForm](/api/use-form-handler/reset-form) |
52+
| setError | SetError | [API - setError](/api/use-form-handler/set-error) |
53+
| setValue | SetValue | [API - setValue](/api/use-form-handler/set-value) |
54+
| triggerValidation | TriggerValidation | [API - triggerValidation](/api/use-form-handler/trigger-validation) |
55+
| values | Record<string,any> | [API - values](/api/use-form-handler/values) |
56+
57+
::: info
58+
As you can see, the interceptor is provided with everything the handler does provide but in a separate context.
59+
:::
60+
61+
#### Expected return
62+
63+
The interceptor expects a type of `boolean` or a `Promise<boolean>`,
64+
return true to proceed setting the value and false if the value should not be set.
65+
66+
#### Example
67+
68+
```vue
69+
<template>
70+
<select v-bind="register('continent')" placeholder="Choose your country">
71+
<option disabled value=null>Choose your continent</option>
72+
<option value="AM">America</option>
73+
<option value="AS">Asia</option>
74+
<option value="EU">Europe</option>
75+
</select>
76+
<select v-bind="register('country')" placeholder="Choose your country">
77+
<option disabled value=null>Choose your country</option>
78+
<option value="CAN">Canada</option>
79+
<option value="USA">United States</option>
80+
<option value="JAP">Japan</option>
81+
<option value="CHN">China</option>
82+
<option value="ESP">Spain</option>
83+
<option value="DEU">Germany</option>
84+
</select>
85+
</template>
86+
<script setup lang="ts">
87+
import { useFormHandler } from 'vue-form-handler'
88+
89+
const interceptor = ({ name, clearField }) => {
90+
if (name === 'continent') {
91+
clearField('country')
92+
}
93+
return true
94+
}
95+
96+
const { register } = useFormHandler({
97+
interceptor
98+
})
99+
</script>
100+
```
101+
102+
### validate: <font size=2>FormValidation</font>
103+
104+
Use this function in the case you'd rather perform a custom/different validation when submitting your form
105+
106+
#### Example
107+
108+
```vue
109+
<template @submit.prevent="handleSubmit(successFn)">
110+
<form>
111+
<input v-bind="register('firstName')">
112+
<input v-bind="register('lastName')">
113+
<input type="number" v-bind="register('age')">
114+
<input type="submit">
115+
</form>
116+
</template>
117+
<script setup lang="ts">
118+
import { useFormHandler } from 'vue-form-handler'
119+
120+
const validation = (values) => values.age && Number(values.age)>=18
121+
const successFn = (form:Record<string,any>) => {
122+
console.log({form})
123+
}
124+
125+
const { register, handleSubmit } = useFormHandler({
126+
validation
127+
})
128+
</script>
129+
```
130+
131+
### validationMode: <font size="2">'onChange' | 'onBlur' | 'onSubmit' | 'always'</font>
132+
133+
This option allows you to configure the validation mode or strategy the handler will follow.
134+
135+
| name | type | description |
136+
|-----------|--------|--------------------------------------------|
137+
| onChange | string | Validation will trigger on the change event with each input, and lead to multiple re-renders. |
138+
| onBlur | string | Validation will trigger on the blur event. |
139+
| onSubmit | string | Validation will trigger on the submit event. |
140+
| always | string | Validation will trigger on change and blur events. |
141+
142+
::: warning
143+
Using the `always` validationMode will have a more significant impact on performance.
144+
:::
145+
146+
## Return
147+
148+
- [clearError](/api/use-form-handler/clear-error)
149+
- [clearField](/api/use-form-handler/clear-field)
150+
- [formState](/api/use-form-handler/form-state)
151+
- [handleSubmit](/api/use-form-handler/handle-submit)
152+
- [modifiedValues](/api/use-form-handler/modified-values)
153+
- [register](/api/use-form-handler/register)
154+
- [resetField](/api/use-form-handler/reset-field)
155+
- [resetForm](/api/use-form-handler/reset-form)
156+
- [setError](/api/use-form-handler/set-error)
157+
- [setValue](/api/use-form-handler/set-value)
158+
- [triggerValidation](/api/use-form-handler/trigger-validation)
159+
- [unregister](/api/use-form-handler/unregister)
160+
- [values](/api/use-form-handler/values)

docs/tutorial.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,23 +129,23 @@ Our current form works but it's not complete at all, we might want to add valida
129129
required: true,
130130
pattern: emailRegExp
131131
})" />
132-
<p class="error" v-show="formState.errors.email">
133-
{{formState.errors.email}}
134-
</p>
132+
<p class="error" v-show="formState.errors.email">
133+
{{formState.errors.email}}
134+
</p>
135135
<input type="password" v-bind="register('password', {
136136
required: true,
137137
pattern: passwordRegExp
138138
})" />
139-
<p class="error" v-show="formState.errors.password">
140-
{{formState.errors.password}}
141-
</p>
142-
<input type="password" v-bind="register('confirmPassword', {
139+
<p class="error" v-show="formState.errors.password">
140+
{{formState.errors.password}}
141+
</p>
142+
<input type="password" v-bind="register('confirmPassword', {
143143
required: true,
144144
pattern: passwordRegExp
145145
})" />
146-
<p class="error" v-show="formState.errors.confirmPassword">
147-
{{formState.errors.confirmPassword}}
148-
</p>
146+
<p class="error" v-show="formState.errors.confirmPassword">
147+
{{formState.errors.confirmPassword}}
148+
</p>
149149
<input type="submit"/>
150150
</form>
151151
</template>
@@ -173,7 +173,7 @@ We can also pass a validation function and specify `validationMode: 'onSubmit'`
173173

174174
## Custom validation
175175

176-
```vue
176+
```vue{20-22}
177177
<template>
178178
<form @submit.prevent="handleSubmit(successFn)">
179179
<input type="email" v-bind="register('email', {
@@ -224,7 +224,7 @@ Submission is really made easy, we can call the function the handler provides us
224224

225225
If the submission fails it will call the error function we pass with the errors if available, if no error function is provided, then it will throw an error that you can catch from the upper context.
226226

227-
```vue
227+
```vue{2,37-42}
228228
<template>
229229
<form @submit.prevent="handleSubmit(successFn, errorFn)">
230230
<input type="email" v-bind="register('email', {

src/logic/refFn.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ export default (name: string, _refs: Refs, values: any) => (fieldRef: any) => {
2525
: fieldRef
2626
}
2727
if (isRadioInput(fieldRef)) {
28-
if (isFirstRegister && fieldRef.checked) {
28+
if (isFirstRegister && !!fieldRef.checked) {
2929
values[name] = fieldRef.value
3030
return
3131
}
3232
fieldRef.checked = (values[name] === fieldRef.value)
3333
return
3434
}
3535
if (isCheckboxInput(fieldRef)) {
36-
if (isFirstRegister) {
37-
values[name] = !!fieldRef.checked
36+
if (isFirstRegister && !!fieldRef.checked) {
37+
values[name] = true
3838
return
3939
}
4040
fieldRef.checked = !!values[name]

src/test/handler.test.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,10 @@ describe('Form handler testing', () => {
4545
expect(formState.errors).toStrictEqual({ field: { error: 'some error' } })
4646
expect(formState.isValid).toBeFalsy()
4747
})
48-
it('Clearing one error of a control programmatically', async () => {
48+
it('Clearing an error programmatically', async () => {
4949
const { formState, setError, clearError } = useFormHandler();
50-
const errors = { error: 'some error', error2: 'some other error' }
51-
setError('field', errors)
52-
expect(formState.isValid).toBeFalsy()
53-
clearError('field', 'error')
54-
expect(formState.errors.field).toStrictEqual({ error2: 'some other error' })
55-
expect(formState.isValid).toBeFalsy()
56-
})
57-
it('Clearing all errors of a control programmatically', async () => {
58-
const { formState, setError, clearError } = useFormHandler();
59-
const errors = { error: 'some error', error2: 'some other error' }
60-
setError('field', errors)
50+
const error = 'This field has an error'
51+
setError('field', error)
6152
expect(formState.isValid).toBeFalsy()
6253
clearError('field')
6354
expect(formState.errors.field).toBeUndefined()

src/types/formHandler.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ComputedRef, Ref } from "vue"
12
import { RegisterOptions, Register } from "./register"
23

34
export interface FormState {
@@ -51,13 +52,13 @@ export type ResetField = (name: string) => void
5152
export type ResetForm = () => void
5253

5354
/** Function to set an error programmatically */
54-
export type SetError = (name: string, error: any, replace?: boolean) => void
55+
export type SetError = (name: string, error: any) => void
5556

5657
/** Function to clear an error programmatically */
57-
export type ClearError = (name?: string, errors?: string | string[]) => void
58+
export type ClearError = (name?: string) => void
5859

5960
/** Function to get the modified values of the form */
60-
export type ModifiedValues = () => Object
61+
export type ModifiedValues = () => Record<string, any>
6162

6263
/** Expected function to be called after a form submitted successfully */
6364
export type HandleSubmitSuccessFn = (values: Object) => void
@@ -96,24 +97,32 @@ export interface InterceptorParams {
9697
/** Function to set an error on a field programmatically */
9798
setError: SetError
9899

100+
/** Function to set the value of a field programmatically */
101+
setValue: SetValue
102+
99103
/** Function to clear one or more errors on a desired field or the whole form*/
100104
clearError: ClearError
101105

106+
/** Function to clear a desired field*/
107+
clearField: ClearField
108+
102109
/** Function that returns the modified values of the form */
103110
modifiedValues: ModifiedValues
104111
}
105112

106-
export type Interceptor = (_: InterceptorParams) => Promise<boolean>
113+
export type Interceptor = (_: InterceptorParams) => Promise<boolean> | boolean
114+
115+
export type FormValidation = (values: Record<string, any>) => Promise<boolean> | boolean
107116

108117
export interface FormHandlerParams {
109118
/** Values to initialize the form */
110-
initialValues?: Record<string, any>
119+
initialValues?: Record<string, any> | Ref<Record<string, any>> | ComputedRef<Record<string, any>>
111120

112121
/** Field change interceptor */
113122
interceptor?: Interceptor
114123

115124
/** Validation function to execute before submitting (when using this individual validations are invalidated) */
116-
validate?: () => Promise<boolean> | boolean
125+
validate?: FormValidation
117126

118127
/** Validation behavior options */
119128
validationMode?: 'onChange' | 'onBlur' | 'onSubmit' | 'always'

0 commit comments

Comments
 (0)