Skip to content

Commit

Permalink
fix: combine field and form level errors
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Aug 13, 2024
1 parent cf7ee3b commit a0ec46d
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 11 deletions.
49 changes: 48 additions & 1 deletion packages/core/src/useForm/useForm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ describe('form validation', () => {
inheritAttrs: false,
setup: (_, { attrs }) => {
const name = (attrs.name || 'test') as string;
const { errorMessage, inputProps } = useTextField({ name, label: name });
const schema = attrs.schema as TypedSchema<any>;
const { errorMessage, inputProps } = useTextField({ name, label: name, schema });

return { errorMessage: errorMessage, inputProps, name };
},
Expand Down Expand Up @@ -633,6 +634,52 @@ describe('form validation', () => {

expect(values).toEqual({ test: 'foo' });
});

test('combines errors from field-level schemas', async () => {
const handler = vi.fn();
const schema: TypedSchema<object, object> = {
async parse() {
return {
errors: [{ path: 'test', messages: ['error'] }],
};
},
};

const fieldSchema: TypedSchema<object, object> = {
async parse() {
return {
errors: [{ path: 'field', messages: ['field error'] }],
};
},
};

await render({
components: { Child: createInputComponent() },
setup() {
const { handleSubmit, getError } = useForm({
schema,
});

return { getError, onSubmit: handleSubmit(handler), fieldSchema };
},
template: `
<form @submit="onSubmit" novalidate>
<Child />
<Child name="field" :schema="fieldSchema" />
<span data-testid="form-err">{{ getError('test') }}</span>
<span data-testid="field-err">{{ getError('field') }}</span>
<button type="submit">Submit</button>
</form>
`,
});

await fireEvent.click(screen.getByText('Submit'));
await flush();
expect(screen.getByTestId('form-err').textContent).toBe('error');
expect(screen.getByTestId('field-err').textContent).toBe('field error');
expect(handler).not.toHaveBeenCalled();
});
});

test('form reset clears errors', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/useForm/useFormActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function useFormActions<TForm extends FormObject = FormObject, TOutput ex
return {
mode: 'schema',
isValid: !allErrors.length,
errors,
errors: allErrors,
output: cloneDeep(output ?? (form.getValues() as unknown as TOutput)),
};
}
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/useTextField/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useLabel } from '../a11y/useLabel';
import { useFormField } from '../useFormField';
import { FieldTypePrefixes } from '../constants';
import { useErrorDisplay } from '../useFormField/useErrorDisplay';
import { TypedSchema } from '../types';

export type TextInputDOMType = 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';

Expand Down Expand Up @@ -47,19 +48,22 @@ export interface TextFieldProps {
required?: boolean;
readonly?: boolean;
disabled?: boolean;

schema?: TypedSchema<string>;
}

export function useTextField(
_props: Reactivify<TextFieldProps>,
_props: Reactivify<TextFieldProps, 'schema'>,
elementRef?: Ref<HTMLInputElement | HTMLTextAreaElement>,
) {
const props = normalizeProps(_props);
const props = normalizeProps(_props, ['schema']);
const inputId = useUniqId(FieldTypePrefixes.TextField);
const inputRef = elementRef || shallowRef<HTMLInputElement>();
const field = useFormField<string | undefined>({
path: props.name,
initialValue: toValue(props.modelValue),
disabled: props.disabled,
schema: props.schema,
});

const { validityDetails } = useInputValidity({ inputRef, field });
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/validation/useInputValidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,8 @@ export function useInputValidity(opts: InputValidityOptions) {
}

function _updateValidity() {
if (schema) {
return validateField(true);
}

if (validationMode === 'native') {
return validateNative(true);
return schema ? validateField(true) : validateNative(true);
}

form?.requestValidation();
Expand Down
3 changes: 1 addition & 2 deletions packages/playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</div>
</FormGroup> -->

<InputText label="Email" name="email" type="email" required />
<InputText label="Email" name="email" type="email" :schema="defineSchema(z.string().email())" />
<InputText label="Other" name="other" required />

<button @click="onSubmit">Submit</button>
Expand All @@ -31,7 +31,6 @@ import { z } from 'zod';
const { getErrors, values, handleSubmit } = useForm({
schema: defineSchema(
z.object({
email: z.string().email(),
other: z.string().min(3),
}),
),
Expand Down

0 comments on commit a0ec46d

Please sign in to comment.