Skip to content

Commit 62d5883

Browse files
committed
Enhance SignupForm with Debounced Validation
1 parent 5e51841 commit 62d5883

File tree

1 file changed

+65
-55
lines changed

1 file changed

+65
-55
lines changed

client/modules/User/components/SignupForm.jsx

Lines changed: 65 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,49 +9,64 @@ import Button from '../../../common/Button';
99
import apiClient from '../../../utils/apiClient';
1010
import useSyncFormTranslations from '../../../common/useSyncFormTranslations';
1111

12-
function asyncValidate(fieldToValidate, value) {
12+
// Debounce utility function
13+
const debounce = (func, delay) => {
14+
let timer;
15+
return (...args) =>
16+
new Promise((resolve) => {
17+
clearTimeout(timer);
18+
timer = setTimeout(() => resolve(func(...args)), delay);
19+
});
20+
};
21+
22+
// API Validation Function
23+
async function asyncValidate(fieldToValidate, value) {
1324
if (!value || value.trim().length === 0) {
1425
return '';
1526
}
16-
const queryParams = {};
17-
queryParams[fieldToValidate] = value;
18-
queryParams.check_type = fieldToValidate;
19-
return apiClient
20-
.get('/signup/duplicate_check', { params: queryParams })
21-
.then((response) => {
22-
if (response.data.exists) {
23-
return response.data.message;
24-
}
25-
return '';
27+
const queryParams = {
28+
[fieldToValidate]: value,
29+
check_type: fieldToValidate
30+
};
31+
32+
try {
33+
const response = await apiClient.get('/signup/duplicate_check', {
34+
params: queryParams
2635
});
36+
return response.data.exists ? response.data.message : '';
37+
} catch (error) {
38+
return 'Error validating field.';
39+
}
2740
}
2841

42+
// Debounced Validators
43+
const debouncedAsyncValidate = debounce(asyncValidate, 300);
44+
2945
function validateUsername(username) {
30-
return asyncValidate('username', username);
46+
return debouncedAsyncValidate('username', username);
3147
}
3248

3349
function validateEmail(email) {
34-
return asyncValidate('email', email);
50+
return debouncedAsyncValidate('email', email);
3551
}
3652

3753
function SignupForm() {
3854
const { t, i18n } = useTranslation();
39-
4055
const dispatch = useDispatch();
41-
function onSubmit(formProps) {
42-
return dispatch(validateAndSignUpUser(formProps));
43-
}
56+
const formRef = useRef(null);
4457
const [showPassword, setShowPassword] = useState(false);
4558
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
46-
const formRef = useRef(null);
47-
const handleVisibility = () => {
48-
setShowPassword(!showPassword);
49-
};
50-
const handleConfirmVisibility = () => {
51-
setShowConfirmPassword(!showConfirmPassword);
52-
};
59+
5360
useSyncFormTranslations(formRef, i18n.language);
5461

62+
const handleVisibility = () => setShowPassword(!showPassword);
63+
const handleConfirmVisibility = () =>
64+
setShowConfirmPassword(!showConfirmPassword);
65+
66+
function onSubmit(formProps) {
67+
return dispatch(validateAndSignUpUser(formProps));
68+
}
69+
5570
return (
5671
<Form
5772
fields={['username', 'email', 'password', 'confirmPassword']}
@@ -62,75 +77,72 @@ function SignupForm() {
6277
formRef.current = form;
6378
return (
6479
<form className="form" onSubmit={handleSubmit}>
80+
{/* Username Field */}
6581
<Field
6682
name="username"
6783
validate={validateUsername}
6884
validateFields={[]}
6985
>
70-
{(field) => (
86+
{({ input, meta }) => (
7187
<div className="form__field">
7288
<label htmlFor="username" className="form__label">
7389
{t('SignupForm.Title')}
7490
</label>
7591
<input
7692
className="form__input"
77-
aria-label={t('SignupForm.TitleARIA')}
7893
type="text"
7994
id="username"
8095
autoComplete="username"
8196
autoCapitalize="none"
82-
{...field.input}
97+
{...input}
8398
/>
84-
{field.meta.touched && field.meta.error && (
85-
<span className="form-error" aria-live="polite">
86-
{field.meta.error}
87-
</span>
99+
{meta.touched && meta.error && (
100+
<span className="form-error">{meta.error}</span>
88101
)}
89102
</div>
90103
)}
91104
</Field>
105+
106+
{/* Email Field */}
92107
<Field name="email" validate={validateEmail} validateFields={[]}>
93-
{(field) => (
108+
{({ input, meta }) => (
94109
<div className="form__field">
95110
<label htmlFor="email" className="form__label">
96111
{t('SignupForm.Email')}
97112
</label>
98113
<input
99114
className="form__input"
100-
aria-label={t('SignupForm.EmailARIA')}
101115
type="email"
102116
id="email"
103117
autoComplete="email"
104-
{...field.input}
118+
{...input}
105119
/>
106-
{field.meta.touched && field.meta.error && (
107-
<span className="form-error" aria-live="polite">
108-
{field.meta.error}
109-
</span>
120+
{meta.touched && meta.error && (
121+
<span className="form-error">{meta.error}</span>
110122
)}
111123
</div>
112124
)}
113125
</Field>
126+
127+
{/* Password Field */}
114128
<Field name="password">
115-
{(field) => (
129+
{({ input, meta }) => (
116130
<div className="form__field">
117131
<label htmlFor="password" className="form__label">
118132
{t('SignupForm.Password')}
119133
</label>
120134
<div className="form__field__password">
121135
<input
122136
className="form__input"
123-
aria-label={t('SignupForm.PasswordARIA')}
124137
type={showPassword ? 'text' : 'password'}
125138
id="password"
126139
autoComplete="new-password"
127-
{...field.input}
140+
{...input}
128141
/>
129142
<button
130143
className="form__eye__icon"
131144
type="button"
132145
onClick={handleVisibility}
133-
aria-hidden="true"
134146
>
135147
{showPassword ? (
136148
<AiOutlineEyeInvisible />
@@ -139,16 +151,16 @@ function SignupForm() {
139151
)}
140152
</button>
141153
</div>
142-
{field.meta.touched && field.meta.error && (
143-
<span className="form-error" aria-live="polite">
144-
{field.meta.error}
145-
</span>
154+
{meta.touched && meta.error && (
155+
<span className="form-error">{meta.error}</span>
146156
)}
147157
</div>
148158
)}
149159
</Field>
160+
161+
{/* Confirm Password Field */}
150162
<Field name="confirmPassword">
151-
{(field) => (
163+
{({ input, meta }) => (
152164
<div className="form__field">
153165
<label htmlFor="confirmPassword" className="form__label">
154166
{t('SignupForm.ConfirmPassword')}
@@ -157,16 +169,14 @@ function SignupForm() {
157169
<input
158170
className="form__input"
159171
type={showConfirmPassword ? 'text' : 'password'}
160-
aria-label={t('SignupForm.ConfirmPasswordARIA')}
161-
id="confirmPassword" // Match the id with htmlFor
172+
id="confirmPassword"
162173
autoComplete="new-password"
163-
{...field.input}
174+
{...input}
164175
/>
165176
<button
166177
className="form__eye__icon"
167178
type="button"
168179
onClick={handleConfirmVisibility}
169-
aria-hidden="true"
170180
>
171181
{showConfirmPassword ? (
172182
<AiOutlineEyeInvisible />
@@ -175,14 +185,14 @@ function SignupForm() {
175185
)}
176186
</button>
177187
</div>
178-
{field.meta.touched && field.meta.error && (
179-
<span className="form-error" aria-live="polite">
180-
{field.meta.error}
181-
</span>
188+
{meta.touched && meta.error && (
189+
<span className="form-error">{meta.error}</span>
182190
)}
183191
</div>
184192
)}
185193
</Field>
194+
195+
{/* Submit Button */}
186196
<Button type="submit" disabled={submitting || invalid || pristine}>
187197
{t('SignupForm.SubmitSignup')}
188198
</Button>

0 commit comments

Comments
 (0)