Skip to content

Commit e0be595

Browse files
committed
✨ Improve useForm hook
1 parent 0ffac49 commit e0be595

3 files changed

Lines changed: 415 additions & 58 deletions

File tree

src/blocks/Form/useForm.ts

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
/* eslint-disable max-lines */
12
import { get } from 'webcoreui'
23

4+
export type ValidationFactory = (formValues: Record<string, string>) => Record<string, boolean>
5+
36
export type FormActions = {
47
validationRules: Record<string, boolean>
8+
validationFactory: ValidationFactory | null
59
isPreventDefault: boolean
610
onErrorCallback: ((invalidFields: string[]) => void) | null
711
preventDefault: () => FormActions
@@ -11,10 +15,11 @@ export type FormActions = {
1115
update: (field: string, value: string | boolean) => FormActions
1216
reset: () => FormActions
1317
clear: () => FormActions
14-
setValidations: (validationRules: Record<string, boolean>) => FormActions
15-
isValidForm: () => boolean,
18+
destroy: () => void
19+
validate: (callback: ValidationFactory) => FormActions
20+
isValid: () => boolean,
1621
useSmartFill: (options?: { patterns?: Record<string, RegExp>, inputs?: Record<string, string> }) => FormActions
17-
onChange: (callback: (formValues: Record<string, string>) => void) => FormActions
22+
onChange: (callback?: (formValues: Record<string, string>) => void) => FormActions
1823
onSubmit: (callback: (formValues: Record<string, string>) => void) => FormActions
1924
onError: (callback: (invalidFields: string[]) => void) => FormActions
2025
}
@@ -43,8 +48,23 @@ export const useForm = (selector: string | HTMLFormElement | null | undefined):
4348
].join(' '))
4449
}
4550

51+
const controller = new AbortController()
52+
const { signal } = controller
53+
54+
const getSubmitButton = (): HTMLButtonElement | null =>
55+
form.querySelector('button[type="submit"], button:not([type])')
56+
57+
const updateSubmitButton = (actions: FormActions) => {
58+
const button = getSubmitButton()
59+
60+
if (button && button.hasAttribute('data-form-managed')) {
61+
button.disabled = !actions.isValid()
62+
}
63+
}
64+
4665
return {
4766
validationRules: {},
67+
validationFactory: null,
4868
isPreventDefault: false,
4969
onErrorCallback: null,
5070
preventDefault() {
@@ -56,7 +76,9 @@ export const useForm = (selector: string | HTMLFormElement | null | undefined):
5676
return form.querySelector(`[name=${field}]`)
5777
},
5878
getInputValue(field) {
59-
return String(new FormData(form).get(field))
79+
const value = new FormData(form).get(field)
80+
81+
return value !== null ? String(value) : null
6082
},
6183
getInputValues() {
6284
const formData = new FormData(form)
@@ -71,6 +93,13 @@ export const useForm = (selector: string | HTMLFormElement | null | undefined):
7193
update(field, value) {
7294
const input = form.querySelector(`[name=${field}]`) as HTMLInputElement
7395

96+
if (!input) {
97+
// eslint-disable-next-line no-console
98+
console.warn(`useForm: no field found with name "${field}"`)
99+
100+
return this
101+
}
102+
74103
if (typeof value === 'boolean') {
75104
input.checked = value
76105
}
@@ -84,6 +113,12 @@ export const useForm = (selector: string | HTMLFormElement | null | undefined):
84113
reset() {
85114
form.reset()
86115

116+
if (this.validationFactory) {
117+
this.validationRules = this.validationFactory(this.getInputValues())
118+
}
119+
120+
updateSubmitButton(this)
121+
87122
return this
88123
},
89124
clear() {
@@ -101,15 +136,35 @@ export const useForm = (selector: string | HTMLFormElement | null | undefined):
101136
}
102137
})
103138

139+
if (this.validationFactory) {
140+
this.validationRules = this.validationFactory(this.getInputValues())
141+
}
142+
143+
updateSubmitButton(this)
144+
104145
return this
105146
},
106-
setValidations(validationRules) {
107-
this.validationRules = validationRules
147+
validate(validationFactory: ValidationFactory) {
148+
const submitButton = getSubmitButton()
149+
150+
if (submitButton && !submitButton.hasAttribute('disabled')) {
151+
submitButton.setAttribute('data-form-managed', '')
152+
}
153+
154+
this.validationFactory = validationFactory
155+
this.validationRules = validationFactory(this.getInputValues())
156+
157+
updateSubmitButton(this)
108158

109159
return this
110160
},
111-
isValidForm() {
112-
return Object.values(this.validationRules).every(key => key)
161+
isValid() {
162+
if (!this.validationFactory) {
163+
return true
164+
}
165+
166+
return Object.keys(this.validationRules).length > 0
167+
&& Object.values(this.validationRules).every(key => key)
113168
},
114169
useSmartFill({ patterns, inputs } = {}) {
115170
const textPatterns = {
@@ -214,14 +269,21 @@ export const useForm = (selector: string | HTMLFormElement | null | undefined):
214269
if (autoFilled) {
215270
event.preventDefault()
216271
}
217-
})
272+
}, { signal })
218273

219274
return this
220275
},
221-
onChange(callback) {
276+
onChange(callback?) {
222277
form.addEventListener('input', () => {
223-
callback?.(this.getInputValues())
224-
})
278+
const formValues = this.getInputValues()
279+
280+
if (this.validationFactory) {
281+
this.validationRules = this.validationFactory(formValues)
282+
}
283+
284+
updateSubmitButton(this)
285+
callback?.(formValues)
286+
}, { signal })
225287

226288
return this
227289
},
@@ -231,25 +293,30 @@ export const useForm = (selector: string | HTMLFormElement | null | undefined):
231293
event.preventDefault()
232294
}
233295

234-
if (this.isValidForm()) {
296+
const valid = this.isValid()
297+
298+
if (valid) {
235299
callback?.(this.getInputValues())
236300
}
237301

238-
if (!this.isValidForm() && this.onErrorCallback) {
302+
if (!valid && this.onErrorCallback) {
239303
const invalidFields = Object
240304
.keys(this.validationRules)
241305
.filter(key => !this.validationRules[key])
242306

243307
this.onErrorCallback(invalidFields)
244308
}
245-
})
309+
}, { signal })
246310

247311
return this
248312
},
249313
onError(callback) {
250314
this.onErrorCallback = callback
251315

252316
return this
317+
},
318+
destroy() {
319+
controller.abort()
253320
}
254321
}
255322
}

src/pages/blocks/form.astro

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,13 @@ const getUserForm = (): FormProps['fields'] => [
209209
.useSmartFill()
210210
.update('email', 'john@doe.com')
211211
.update('message', 'You can update values dynamically')
212+
.validate(formValues => ({
213+
email: !!formValues.email.includes('john'),
214+
message: !!(formValues.message?.length > 10)
215+
}))
212216
.onChange(formValues => {
213-
const validationRules = {
214-
email: !!formValues.email.includes('john'),
215-
message: !!(formValues.message?.length > 10)
216-
}
217-
218-
form.setValidations(validationRules)
217+
// eslint-disable-next-line no-console
218+
console.log('Form values changed to:', formValues)
219219
})
220220
.onSubmit(formValues => {
221221
toast({

0 commit comments

Comments
 (0)