Skip to content

Commit 0876d13

Browse files
committed
Delay error and test
1 parent d232c0e commit 0876d13

File tree

5 files changed

+173
-32
lines changed

5 files changed

+173
-32
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func useForm<FieldName>(
9393
context: Any? = nil,
9494
shouldUnregister: Bool = true,
9595
criteriaMode: CriteriaMode = .all,
96-
delayError: Bool = false
96+
delayErrorInNanoseconds: UInt64 = 0
9797
) -> FormControl<FieldName> where FieldName: Hashable
9898
```
9999

@@ -189,4 +189,4 @@ It wraps a call of `useController` inside the `hookBody`. Like `useController`,
189189

190190
[MIT © Dung Nguyen](LICENSE)
191191

192-
---
192+
---

Sources/FormHook/Form.swift

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import SwiftUI
1010
import Hooks
1111

1212
public class FormControl<FieldName> where FieldName: Hashable {
13+
var currentErrorNotifyTask: Task<Void, Error>?
14+
1315
var options: FormOption<FieldName>
1416
var fields: [FieldName: FieldProtocol]
1517
@MainActor @Binding
@@ -100,7 +102,7 @@ public class FormControl<FieldName> where FieldName: Hashable {
100102
var errorFields: Set<FieldName> = .init()
101103
var messages: [FieldName: [String]] = [:]
102104
var isOveralValid = true
103-
105+
104106
for (key, field) in fields {
105107
group.addTask {
106108
let (isValid, messages) = await field.computeMessages()
@@ -171,15 +173,35 @@ public class FormControl<FieldName> where FieldName: Hashable {
171173
}
172174
instantFormState.isValid = isOveralValid
173175
instantFormState.isSubmitSuccessful = errors.errorFields.isEmpty
174-
instantFormState.errors = errors
176+
currentErrorNotifyTask?.cancel()
177+
if options.delayErrorInNanoseconds == 0 || isOveralValid {
178+
currentErrorNotifyTask = nil
179+
instantFormState.errors = errors
180+
} else {
181+
currentErrorNotifyTask = Task {
182+
try await Task.sleep(nanoseconds: options.delayErrorInNanoseconds)
183+
instantFormState.errors = errors
184+
await syncFormState()
185+
}
186+
}
175187
instantFormState.submitCount += 1
176188
instantFormState.submissionState = .submitted
177189
await syncFormState()
178190
} catch {
179191
instantFormState.isValid = isOveralValid
180192
instantFormState.submissionState = preservedSubmissionState
181193
instantFormState.isSubmitSuccessful = false
182-
instantFormState.errors = errors
194+
currentErrorNotifyTask?.cancel()
195+
if options.delayErrorInNanoseconds == 0 || isOveralValid {
196+
currentErrorNotifyTask = nil
197+
instantFormState.errors = errors
198+
} else {
199+
currentErrorNotifyTask = Task {
200+
try await Task.sleep(nanoseconds: options.delayErrorInNanoseconds)
201+
instantFormState.errors = errors
202+
await syncFormState()
203+
}
204+
}
183205
instantFormState.submitCount += 1
184206
instantFormState.submissionState = .submitted
185207
await syncFormState()
@@ -331,7 +353,16 @@ public class FormControl<FieldName> where FieldName: Hashable {
331353
instantFormState.isValid = false
332354
}
333355
instantFormState.isValidating = false
334-
instantFormState.errors = errors
356+
currentErrorNotifyTask?.cancel()
357+
if options.delayErrorInNanoseconds == 0 {
358+
instantFormState.errors = errors
359+
} else {
360+
currentErrorNotifyTask = Task {
361+
try await Task.sleep(nanoseconds: options.delayErrorInNanoseconds)
362+
instantFormState.errors = errors
363+
await syncFormState()
364+
}
365+
}
335366
await syncFormState()
336367
return isValid
337368
}
@@ -356,7 +387,17 @@ extension FormControl {
356387
instantFormState.formValues.update(other: formValues)
357388
case .failure(let e):
358389
isValid = false
359-
instantFormState.errors = e
390+
currentErrorNotifyTask?.cancel()
391+
if options.delayErrorInNanoseconds == 0 || isValid {
392+
currentErrorNotifyTask = nil
393+
instantFormState.errors = e
394+
} else {
395+
currentErrorNotifyTask = Task {
396+
try await Task.sleep(nanoseconds: options.delayErrorInNanoseconds)
397+
instantFormState.errors = e
398+
await syncFormState()
399+
}
400+
}
360401
}
361402
instantFormState.isValid = isValid
362403
} else {
@@ -378,7 +419,17 @@ extension FormControl {
378419
}
379420
return (true, errors)
380421
}
381-
instantFormState.errors = errors
422+
currentErrorNotifyTask?.cancel()
423+
if options.delayErrorInNanoseconds == 0 || isValid {
424+
currentErrorNotifyTask = nil
425+
instantFormState.errors = errors
426+
} else {
427+
currentErrorNotifyTask = Task {
428+
try await Task.sleep(nanoseconds: options.delayErrorInNanoseconds)
429+
instantFormState.errors = errors
430+
await syncFormState()
431+
}
432+
}
382433
instantFormState.isValid = isValid
383434
}
384435
await syncFormState()
@@ -460,23 +511,23 @@ public struct ContextualForm<Content, FieldName>: View where Content: View, Fiel
460511
let resolver: Resolver<FieldName>?
461512
let context: Any?
462513
let shouldUnregister: Bool
463-
let delayError: Bool
514+
let delayErrorInNanoseconds: UInt64
464515
let contentBuilder: (FormControl<FieldName>) -> Content
465516

466517
public init(mode: Mode = .onSubmit,
467518
reValidateMode: ReValidateMode = .onChange,
468519
resolver: Resolver<FieldName>? = nil,
469520
context: Any? = nil,
470521
shouldUnregister: Bool = true,
471-
delayError: Bool = false,
522+
delayErrorInNanoseconds: UInt64 = 0,
472523
@ViewBuilder content: @escaping (FormControl<FieldName>) -> Content
473524
) {
474525
self.mode = mode
475526
self.reValidateMode = reValidateMode
476527
self.resolver = resolver
477528
self.context = context
478529
self.shouldUnregister = shouldUnregister
479-
self.delayError = delayError
530+
self.delayErrorInNanoseconds = delayErrorInNanoseconds
480531
self.contentBuilder = content
481532
}
482533

@@ -488,7 +539,7 @@ public struct ContextualForm<Content, FieldName>: View where Content: View, Fiel
488539
resolver: resolver,
489540
context: context,
490541
shouldUnregister: shouldUnregister,
491-
delayError: delayError
542+
delayErrorInNanoseconds: delayErrorInNanoseconds
492543
)
493544
Context.Provider(value: form) {
494545
contentBuilder(form)

Sources/FormHook/Hook/UseForm.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public func useForm<FieldName>(
1414
resolver: Resolver<FieldName>? = nil,
1515
context: Any? = nil,
1616
shouldUnregister: Bool = true,
17-
delayError: Bool = false
17+
delayErrorInNanoseconds: UInt64 = 0
1818
) -> FormControl<FieldName> where FieldName: Hashable {
1919
useForm(
2020
FormOption(
@@ -23,7 +23,7 @@ public func useForm<FieldName>(
2323
resolver: resolver,
2424
context: context,
2525
shouldUnregister: shouldUnregister,
26-
delayError: delayError
26+
delayErrorInNanoseconds: delayErrorInNanoseconds
2727
)
2828
)
2929
}
@@ -41,21 +41,21 @@ public struct FormOption<FieldName> where FieldName: Hashable {
4141
var resolver: Resolver<FieldName>?
4242
var context: Any?
4343
var shouldUnregister: Bool
44-
var delayError: Bool
44+
var delayErrorInNanoseconds: UInt64
4545

4646
init(mode: Mode,
4747
reValidateMode: ReValidateMode,
4848
@_implicitSelfCapture resolver: Resolver<FieldName>?,
4949
context: Any?,
5050
shouldUnregister: Bool,
51-
delayError: Bool
51+
delayErrorInNanoseconds: UInt64
5252
) {
5353
self.mode = mode
5454
self.reValidateMode = reValidateMode
5555
self.resolver = resolver
5656
self.context = context
5757
self.shouldUnregister = shouldUnregister
58-
self.delayError = delayError
58+
self.delayErrorInNanoseconds = delayErrorInNanoseconds
5959
}
6060
}
6161

0 commit comments

Comments
 (0)