Skip to content

Commit f390c58

Browse files
committed
feat: update the rules for verification.
1 parent 1f2fa06 commit f390c58

File tree

4 files changed

+268
-200
lines changed

4 files changed

+268
-200
lines changed

playground/src/app/(demo)/form/modules/toast.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { toast } from 'soybean-react-ui';
22

33
export function showToastCode(title: string, values: any) {
44
toast(title, {
5+
className: '!w-[400px]',
56
description: (
67
<pre className="mt-2 w-[360px] max-sm:w-screen rounded-md bg-neutral-950 p-4">
78
<code className="text-white">{JSON.stringify(values, null, 2)}</code>
89
</pre>
910
),
10-
className: '!w-[400px]',
1111
position: 'top-center'
1212
});
1313
}

playground/src/app/(demo)/form/modules/validate.tsx

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { useEffect } from 'react';
4-
import { Card, Form, FormField, useFieldErrors, useForm } from 'soybean-react-ui';
4+
import { Button, Card, Form, FormField, useFieldErrors, useForm } from 'soybean-react-ui';
55

66
import { DemoInput } from './DemoComponents';
77
import { showToastCode } from './toast';
@@ -14,17 +14,23 @@ type Inputs = {
1414
const Validate = () => {
1515
const [form] = useForm<Inputs>();
1616

17-
const errors = useFieldErrors(form);
17+
// const errors = useFieldErrors(form);
1818

19-
useEffect(() => {
20-
// showToastCode('all Errors', errors);
21-
}, [errors]);
19+
// useEffect(() => {
20+
// // showToastCode('all Errors', errors);
21+
// }, [errors]);
2222

2323
return (
2424
<Card title="Validate Fields">
2525
<Form
2626
className="w-[480px] max-sm:w-full space-y-4"
2727
form={form}
28+
onFinish={values => {
29+
showToastCode('You submitted the following values success Validate', values);
30+
}}
31+
onFinishFailed={errors => {
32+
showToastCode('You failed to submit the form failed Validate', errors);
33+
}}
2834
>
2935
<FormField
3036
label="Username"
@@ -43,7 +49,8 @@ const Validate = () => {
4349
name="age"
4450
rules={[
4551
{ message: 'Age is required', required: true },
46-
{ message: 'Age must be at least 18', min: 18, type: 'number' }
52+
{ message: 'Age must be at least 18', min: 18, type: 'number' },
53+
{ max: 35, message: 'Age must be less than 35', type: 'number' }
4754
]}
4855
>
4956
<DemoInput
@@ -56,11 +63,27 @@ const Validate = () => {
5663
label="Password"
5764
name="password"
5865
rules={[
59-
{ message: 'Password must be at least 8 characters', minLength: 8, type: 'string' },
6066
{
61-
validator: (_, value) => {
62-
if (value.includes('123')) {
63-
return 'Password cannot contain 123';
67+
message: 'Password must be at least 8 characters and contain at least one letter and one number',
68+
pattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/
69+
}
70+
]}
71+
>
72+
<DemoInput
73+
name="password"
74+
placeholder="Password"
75+
/>
76+
</FormField>
77+
78+
<FormField
79+
label="Password2"
80+
name="password2"
81+
rules={[
82+
{
83+
validator: (_, value, values) => {
84+
console.log('value', value);
85+
if (value !== values.password) {
86+
return 'Password must be the same';
6487
}
6588

6689
return null;
@@ -69,10 +92,65 @@ const Validate = () => {
6992
]}
7093
>
7194
<DemoInput
72-
name="password"
73-
placeholder="Password"
95+
name="password2"
96+
placeholder="Password2"
7497
/>
7598
</FormField>
99+
100+
<FormField
101+
label="Username2"
102+
name="username2"
103+
rules={[
104+
{
105+
// 异步校验,延迟 1000ms
106+
validator: async (_, value) => {
107+
console.log('start validate username:', value);
108+
109+
await new Promise(r => setTimeout(r, 1000));
110+
111+
if (!value) {
112+
return 'Username2 is required';
113+
}
114+
115+
if (value.toLowerCase() === 'admin') {
116+
return 'This username2 is not allowed';
117+
}
118+
119+
return null; // 校验通过
120+
}
121+
}
122+
]}
123+
>
124+
<DemoInput
125+
name="username2"
126+
placeholder="Username2"
127+
/>
128+
</FormField>
129+
130+
<FormField
131+
label="Email"
132+
name="email"
133+
validateTrigger={['onChange', 'onBlur']}
134+
rules={[
135+
{
136+
message: 'Please enter a valid email',
137+
type: 'email',
138+
validateTrigger: 'onBlur' // 单个
139+
},
140+
{
141+
message: 'At least 6 characters',
142+
minLength: 6,
143+
validateTrigger: ['onChange', 'onBlur'] // 多个
144+
}
145+
]}
146+
>
147+
<DemoInput
148+
name="email"
149+
placeholder="Email"
150+
/>
151+
</FormField>
152+
153+
<Button type="submit">Submit</Button>
76154
</Form>
77155
</Card>
78156
);

primitives/filed-form /src/createStore.ts

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ class FormStore {
662662
if (!wasValidating) {
663663
this._validating.add(key);
664664
this._validated.delete(key);
665-
this.enqueueNotify([key], ChangeTag.Validating, true);
665+
this.enqueueNotify([key], ChangeTag.Validating);
666666
this.triggerOnFieldsChange([key]);
667667
}
668668

@@ -672,9 +672,10 @@ class FormStore {
672672
let warns: string[] = [];
673673

674674
try {
675-
({ errors, warns } = await runRulesWithMode(value, rules, opts?.mode || 'parallelAll'));
675+
({ errors, warns } = await runRulesWithMode(value, rules, opts?.mode || 'parallelAll', this._store));
676676
} catch (e: any) {
677677
// 4) 兜底:规则抛错也要转成错误信息(避免“莫名其妙通过/卡住”)
678+
console.error('e', e);
678679
errors = [String(e?.message ?? e)];
679680
}
680681

@@ -713,7 +714,7 @@ class FormStore {
713714
needFieldsChange = true;
714715
}
715716

716-
if (mask) this.enqueueNotify([key], mask, true);
717+
if (mask) this.enqueueNotify([key], mask);
717718
if (needFieldsChange) this.triggerOnFieldsChange([key]);
718719

719720
return errors.length === 0;
@@ -729,23 +730,41 @@ class FormStore {
729730
});
730731
};
731732

732-
async validateFields(names?: NamePath[], opts?: ValidateOptions) {
733-
const list = names?.length ? names : Array.from(this._dirty);
733+
async validateFields(names?: NamePath[], opts?: ValidateOptions & { dirty?: boolean }) {
734+
let list: string[];
735+
736+
if (names && names.length > 0) {
737+
const normalized = names.map(n => keyOfName(n));
738+
if (opts?.dirty) {
739+
// 只取 names 中 dirty 的
740+
list = normalized.filter(k => this._dirty.has(k));
741+
} else {
742+
// 全部 names
743+
list = normalized;
744+
}
745+
} else if (opts?.dirty) {
746+
// 没有传 names → 校验所有 dirty 的
747+
list = Array.from(this._dirty);
748+
} else {
749+
// 没有传 names → 校验所有字段
750+
list = this._fieldEntities.map(e => keyOfName(e.name));
751+
}
752+
734753
if (!list.length) return true;
735754

755+
// 标记 validating
736756
this.transaction(() => {
737-
for (const n of list) {
738-
const k = keyOfName(n);
757+
for (const k of list) {
739758
if (!this._validating.has(k)) {
740759
this._validating.add(k);
741760
this.enqueueNotify([k], ChangeTag.Validating);
742761
}
743762
}
744763
});
745764

746-
const ok = await Promise.all(list.map(n => this.validateField(n, opts)));
765+
const results = await Promise.all(list.map(n => this.validateField(n, opts)));
747766

748-
return ok.every(Boolean);
767+
return results.every(Boolean);
749768
}
750769

751770
// ------------------------------------------------
@@ -914,10 +933,16 @@ class FormStore {
914933
};
915934
}
916935
private submit = () => {
917-
this.validateFields().then(ok => {
918-
if (ok) this._callbacks.onFinish?.(this._store);
919-
else this._callbacks.onFinishFailed?.(this.buildFailedPayload());
920-
});
936+
console.log('submit');
937+
this.validateFields()
938+
.then(ok => {
939+
console.log('ok', ok);
940+
if (ok) this._callbacks.onFinish?.(this._store);
941+
else this._callbacks.onFinishFailed?.(this.buildFailedPayload());
942+
})
943+
.catch(err => {
944+
console.log('err', err);
945+
});
921946
};
922947

923948
private destroyForm = (clearOnDestroy?: boolean) => {
@@ -991,14 +1016,14 @@ class FormStore {
9911016
}
9921017
}
9931018

994-
private enqueueNotify(names?: NamePath[] | string[], mask: ChangeMask = ChangeTag.All, isImmediate = false) {
1019+
private enqueueNotify(names?: NamePath[] | string[], mask: ChangeMask = ChangeTag.All) {
9951020
if (!names) this.markPending('*', ChangeTag.All);
9961021
else for (const n of names) this.markPending(keyOfName(n), mask);
9971022

998-
this.scheduleFlush(isImmediate);
1023+
this.scheduleFlush();
9991024
}
10001025

1001-
private scheduleFlush(isImmediate = false) {
1026+
private scheduleFlush() {
10021027
if (this._txDepth > 0) return; // During the transaction, do not flush; wait for commit.
10031028

10041029
if (!this._flushScheduled) {

0 commit comments

Comments
 (0)