Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rename-isvalidated-to-isuservalidated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@formwerk/core': minor
---

refactor: rename `isValidated` to `isUserValidated` to clarify that it tracks user-initiated validation
24 changes: 12 additions & 12 deletions packages/core/src/useFormField/useFieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export type FieldState<TValue> = {
isDirty: Ref<boolean>;
isBlurred: Ref<boolean>;
isValid: Ref<boolean>;
isValidated: Ref<boolean>;
isUserValidated: Ref<boolean>;
isDisabled: Ref<boolean>;
errors: Ref<string[]>;
errorMessage: Ref<string>;
Expand All @@ -57,7 +57,7 @@ export type FieldState<TValue> = {
setTouched: (touched: boolean) => void;
setBlurred: (blurred: boolean) => void;
setErrors: (messages: Arrayable<string>) => void;
setIsValidated: (isValidated: boolean) => void;
setIsUserValidated: (isUserValidated: boolean) => void;
form?: FormContext | null;
};

Expand All @@ -75,7 +75,7 @@ export function useFieldState<TValue = unknown>(opts?: Partial<FieldStateInit<TV
const { fieldValue, pathlessValue, setValue } = useFieldValue(getPath, form, initialValue);
const { isTouched, pathlessTouched, setTouched } = useFieldTouched(getPath, form);
const { isBlurred, pathlessBlurred, setBlurred } = useFieldBlurred(getPath, form);
const { isValidated, setIsValidated } = useFieldIsValidated();
const { isUserValidated, setIsUserValidated } = useFieldIsValidated();

const { errors, setErrors, isValid, errorMessage, pathlessValidity, submitErrors, submitErrorMessage } =
useFieldValidity(getPath, isDisabled, form);
Expand Down Expand Up @@ -136,7 +136,7 @@ export function useFieldState<TValue = unknown>(opts?: Partial<FieldStateInit<TV
isBlurred: readonly(isBlurred) as Ref<boolean>,
isDirty,
isValid,
isValidated,
isUserValidated,
errors,
errorMessage,
isDisabled,
Expand All @@ -148,7 +148,7 @@ export function useFieldState<TValue = unknown>(opts?: Partial<FieldStateInit<TV
setTouched,
setBlurred,
setErrors,
setIsValidated,
setIsUserValidated,
submitErrors,
submitErrorMessage,
};
Expand All @@ -171,7 +171,7 @@ export function useFieldState<TValue = unknown>(opts?: Partial<FieldStateInit<TV
});

form.onSubmitDone(() => {
setIsValidated(true);
setIsUserValidated(true);
});

tryOnScopeDispose(() => {
Expand Down Expand Up @@ -503,19 +503,19 @@ export function resolveFieldState<TValue = unknown, TInitialValue = TValue>(
}

/**
* Tracks whether the field has been validated.
* Tracks whether the field has been validated by user interaction.
* This is useful for determining whether to show validation errors or not.
*/
export function useFieldIsValidated() {
// Right now, there is no need to track it in a form.
const isValidated = ref(false);
const isUserValidated = ref(false);

function setIsValidated(value: boolean) {
isValidated.value = value;
function setIsUserValidated(value: boolean) {
isUserValidated.value = value;
}

return {
isValidated,
setIsValidated,
isUserValidated,
setIsUserValidated,
};
Comment on lines 509 to 520
}
72 changes: 36 additions & 36 deletions packages/core/src/useFormField/useFormField.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,27 +295,27 @@ test('validate warns and skips validation on a disabled field', async () => {
consoleWarnSpy.mockRestore();
});

describe('isValidated state', () => {
test('field starts with isValidated as false', async () => {
const { isValidated } = renderSetup(() => {
describe('isUserValidated state', () => {
test('field starts with isUserValidated as false', async () => {
const { isUserValidated } = renderSetup(() => {
return useFormField({ label: 'Field', initialValue: 'bar' }).state;
});

expect(isValidated.value).toBe(false);
expect(isUserValidated.value).toBe(false);
});

test('isValidated remains false after programmatic validation without schema', async () => {
const { isValidated, validate } = renderSetup(() => {
test('isUserValidated remains false after programmatic validation without schema', async () => {
const { isUserValidated, validate } = renderSetup(() => {
return useFormField({ label: 'Field', initialValue: 'bar' }).state;
});

expect(isValidated.value).toBe(false);
expect(isUserValidated.value).toBe(false);
await validate();
expect(isValidated.value).toBe(false); // Should remain false - programmatic validation
expect(isUserValidated.value).toBe(false); // Should remain false - programmatic validation
});

test('isValidated remains false after programmatic validation with schema', async () => {
const { isValidated, validate } = renderSetup(() => {
test('isUserValidated remains false after programmatic validation with schema', async () => {
const { isUserValidated, validate } = renderSetup(() => {
return useFormField({
label: 'Field',
initialValue: 'bar',
Expand All @@ -325,13 +325,13 @@ describe('isValidated state', () => {
}).state;
});

expect(isValidated.value).toBe(false);
expect(isUserValidated.value).toBe(false);
await validate();
expect(isValidated.value).toBe(false); // Should remain false - programmatic validation
expect(isUserValidated.value).toBe(false); // Should remain false - programmatic validation
});

test('isValidated remains false after programmatic validation with errors', async () => {
const { isValidated, validate } = renderSetup(() => {
test('isUserValidated remains false after programmatic validation with errors', async () => {
const { isUserValidated, validate } = renderSetup(() => {
return useFormField({
label: 'Field',
initialValue: '',
Expand All @@ -341,24 +341,24 @@ describe('isValidated state', () => {
}).state;
});

expect(isValidated.value).toBe(false);
expect(isUserValidated.value).toBe(false);
await validate(true);
expect(isValidated.value).toBe(false); // Should remain false - programmatic validation
expect(isUserValidated.value).toBe(false); // Should remain false - programmatic validation
});

test('isValidated can be set manually', async () => {
const { isValidated, setIsValidated } = renderSetup(() => {
test('isUserValidated can be set manually', async () => {
const { isUserValidated, setIsUserValidated } = renderSetup(() => {
return useFormField({ label: 'Field', initialValue: 'bar' }).state;
});

expect(isValidated.value).toBe(false);
setIsValidated(true);
expect(isValidated.value).toBe(true);
setIsValidated(false);
expect(isValidated.value).toBe(false);
expect(isUserValidated.value).toBe(false);
setIsUserValidated(true);
expect(isUserValidated.value).toBe(true);
setIsUserValidated(false);
expect(isUserValidated.value).toBe(false);
});

test('isValidated becomes true after form submit attempt', async () => {
test('isUserValidated becomes true after form submit attempt', async () => {
const { form, field } = renderSetup(
() => {
const form = useForm({
Expand All @@ -380,32 +380,32 @@ describe('isValidated state', () => {
},
);

expect(field.state.isValidated.value).toBe(false);
expect(field.state.isUserValidated.value).toBe(false);

// Submit attempt is a user interaction
const handleSubmit = form.handleSubmit(async () => {});
await handleSubmit();

expect(field.state.isValidated.value).toBe(true);
expect(field.state.isUserValidated.value).toBe(true);
});

test('isValidated is independent for pathless fields', async () => {
test('isUserValidated is independent for pathless fields', async () => {
const { field1, field2 } = renderSetup(() => {
const field1 = useFormField({ label: 'Field1', initialValue: 'bar' });
const field2 = useFormField({ label: 'Field2', initialValue: 'baz' });
return { field1, field2 };
});

expect(field1.state.isValidated.value).toBe(false);
expect(field2.state.isValidated.value).toBe(false);
expect(field1.state.isUserValidated.value).toBe(false);
expect(field2.state.isUserValidated.value).toBe(false);

// Manually set isValidated (simulating user interaction)
field1.state.setIsValidated(true);
expect(field1.state.isValidated.value).toBe(true);
expect(field2.state.isValidated.value).toBe(false);
// Manually set isUserValidated (simulating user interaction)
field1.state.setIsUserValidated(true);
expect(field1.state.isUserValidated.value).toBe(true);
expect(field2.state.isUserValidated.value).toBe(false);

field2.state.setIsValidated(true);
expect(field1.state.isValidated.value).toBe(true);
expect(field2.state.isValidated.value).toBe(true);
field2.state.setIsUserValidated(true);
expect(field1.state.isUserValidated.value).toBe(true);
expect(field2.state.isUserValidated.value).toBe(true);
});
});
12 changes: 6 additions & 6 deletions packages/core/src/useFormField/useFormField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ export type ExposedField<TValue> = {
isValid: Ref<boolean>;

/**
* Whether the field is validated, used to determine whether to show validation errors or not.
* Whether the field has been validated by user interaction (blur, change, or submit), used to determine whether to show validation errors or not.
*/
isValidated: Ref<boolean>;
isUserValidated: Ref<boolean>;

/**
* Whether the field is disabled.
Expand All @@ -141,9 +141,9 @@ export type ExposedField<TValue> = {
setBlurred: (blurred: boolean) => void;

/**
* Sets the validated state for the field.
* Sets the user-validated state for the field.
*/
setIsValidated: (isValidated: boolean) => void;
setIsUserValidated: (isUserValidated: boolean) => void;

/**
* Sets the value for the field.
Expand Down Expand Up @@ -186,7 +186,7 @@ export function exposeField<TReturns extends object, TValue>(
isTouched: field.state.isTouched,
isBlurred: field.state.isBlurred,
isValid: field.state.isValid,
isValidated: field.state.isValidated,
isUserValidated: field.state.isUserValidated,
isDisabled: field.state.isDisabled,
labelProps: field.labelProps,
descriptionProps: field.descriptionProps,
Expand All @@ -202,7 +202,7 @@ export function exposeField<TReturns extends object, TValue>(
: field.state.setErrors,
setTouched: field.state.setTouched,
setBlurred: field.state.setBlurred,
setIsValidated: field.state.setIsValidated,
setIsUserValidated: field.state.setIsUserValidated,
setValue: field.state.setValue,
validate: (mutate = true) => field.state.validate(mutate),
...obj,
Expand Down
Loading
Loading