Skip to content

Commit 746c7dd

Browse files
committed
feat(Form): add clearFieldsValidation and setFieldError methods
1 parent 75d519d commit 746c7dd

File tree

3 files changed

+165
-17
lines changed

3 files changed

+165
-17
lines changed

.changeset/funny-oranges-hope.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': minor
3+
---
4+
5+
Add `clearFieldsValidation()` and `setFieldError()` methods to form to replace deprecated `setFields()`.

src/components/form/Form/submit.test.tsx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,123 @@ describe('<Form />', () => {
250250
expect(resetButton).toBeDisabled();
251251
});
252252
});
253+
254+
it('should clear validation errors using clearFieldsValidation', async () => {
255+
const onSubmit = jest.fn(() => Promise.reject('Submit Error'));
256+
257+
const { getByRole, getByText, formInstance } = renderWithForm(
258+
<>
259+
<Form.Item
260+
name="test"
261+
label="Test"
262+
rules={[
263+
{
264+
validator(rule, value) {
265+
return value
266+
? Promise.resolve()
267+
: Promise.reject('Field is required');
268+
},
269+
},
270+
]}
271+
>
272+
<TextInput />
273+
</Form.Item>
274+
275+
<SubmitButton>Submit</SubmitButton>
276+
</>,
277+
{ formProps: { onSubmit } },
278+
);
279+
280+
const input = getByRole('textbox');
281+
const submitButton = getByRole('button', { name: 'Submit' });
282+
283+
// Initial state: no validation error
284+
expect(() => getByText('Field is required')).toThrow();
285+
286+
// Click submit and check for validation error
287+
await userEvents.click(submitButton);
288+
289+
await waitFor(() => {
290+
expect(getByText('Field is required')).toBeInTheDocument();
291+
});
292+
293+
// Clear all validation errors
294+
await act(async () => {
295+
formInstance.clearFieldsValidation();
296+
});
297+
298+
await waitFor(() => {
299+
expect(() => getByText('Field is required')).toThrow(); // Error should be gone
300+
});
301+
302+
// Click submit again to re-trigger validation error
303+
await userEvents.click(submitButton);
304+
305+
await waitFor(() => {
306+
expect(getByText('Field is required')).toBeInTheDocument();
307+
});
308+
309+
// Clear validation error for specific field
310+
await act(async () => {
311+
formInstance.clearFieldsValidation(['test']);
312+
});
313+
314+
await waitFor(() => {
315+
expect(() => getByText('Field is required')).toThrow(); // Error should be gone
316+
});
317+
});
318+
319+
it('should set validation error using setFieldError', async () => {
320+
const onSubmit = jest.fn(() => Promise.resolve());
321+
322+
const { getByRole, getByText, formInstance } = renderWithForm(
323+
<>
324+
<Form.Item
325+
name="test"
326+
label="Test"
327+
rules={[
328+
{
329+
validator(rule, value) {
330+
return value
331+
? Promise.resolve()
332+
: Promise.reject('Field is required');
333+
},
334+
},
335+
]}
336+
>
337+
<TextInput />
338+
</Form.Item>
339+
340+
<SubmitButton>Submit</SubmitButton>
341+
</>,
342+
{ formProps: { onSubmit } },
343+
);
344+
345+
const input = getByRole('textbox');
346+
const submitButton = getByRole('button', { name: 'Submit' });
347+
348+
// Initial state: no validation error
349+
expect(() => getByText('Custom Error Message')).toThrow();
350+
351+
// Set validation error programmatically
352+
await act(async () => {
353+
formInstance.setFieldError('test', 'Custom Error Message');
354+
});
355+
356+
await waitFor(() => {
357+
expect(getByText('Custom Error Message')).toBeInTheDocument();
358+
});
359+
360+
// Type into input and verify error remains
361+
await act(async () => {
362+
await userEvents.type(input, 'test');
363+
});
364+
365+
// Typing remove the error
366+
expect(() => getByText('Custom Error Message')).toThrow();
367+
368+
await userEvents.click(submitButton);
369+
370+
expect(() => getByText('Field is required')).toThrow();
371+
});
253372
});

src/components/form/Form/use-form.tsx

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -217,25 +217,23 @@ export class CubeFormInstance<
217217
}
218218

219219
resetFields(names?: (keyof T & string)[], skipRender?: boolean): void {
220-
Object.values(this.fields).forEach((field) => {
221-
if (!field || (names && !names?.includes(field.name))) {
220+
names = names ?? Object.keys(this.fields);
221+
222+
names.forEach((fieldName) => {
223+
const field = this.fields[fieldName];
224+
225+
if (!field) {
222226
return;
223227
}
224228

225-
const defaultValue = this.defaultValues[field.name] ?? undefined;
229+
const defaultValue = this.defaultValues[fieldName] ?? undefined;
226230

227231
field.value = defaultValue;
228-
field.errors = [];
229232
field.touched = false;
230-
field.status = undefined;
231233
field.inputValue = defaultValue;
232-
233-
// reject all ongoing validations
234-
if (!field.validationId) {
235-
field.validationId = 1;
236-
} else {
237-
field.validationId++;
238-
}
234+
field.errors = [];
235+
field.status = undefined;
236+
field.validationId = (field.validationId ?? 0) + 1;
239237
});
240238

241239
if (!skipRender) {
@@ -262,11 +260,7 @@ export class CubeFormInstance<
262260
field.validating = true;
263261
field.status = undefined;
264262

265-
if (!field.validationId) {
266-
field.validationId = 1;
267-
} else {
268-
field.validationId++;
269-
}
263+
field.validationId = (field.validationId ?? 0) + 1;
270264

271265
const validationId = field.validationId;
272266

@@ -415,6 +409,36 @@ export class CubeFormInstance<
415409
this.forceReRender();
416410
}
417411

412+
clearFieldsValidation(names?: (keyof T & string)[], skipRender?: boolean) {
413+
(names || Object.keys(this.fields)).forEach((name) => {
414+
const field = this.getFieldInstance(name);
415+
416+
if (!field) return;
417+
418+
field.errors = [];
419+
field.status = undefined;
420+
field.validationId = (field.validationId ?? 0) + 1;
421+
});
422+
423+
if (!skipRender) {
424+
this.forceReRender();
425+
}
426+
}
427+
428+
setFieldError(name: keyof T & string, error: string, skipRender?: boolean) {
429+
const field = this.getFieldInstance(name);
430+
431+
if (!field || !error.trim()) return;
432+
433+
field.errors = [error];
434+
field.status = 'invalid';
435+
field.validationId = (field.validationId ?? 0) + 1;
436+
437+
if (!skipRender) {
438+
this.forceReRender();
439+
}
440+
}
441+
418442
setSubmitting(isSubmitting: boolean) {
419443
if (this.isSubmitting === isSubmitting) return;
420444

0 commit comments

Comments
 (0)