Skip to content

Commit

Permalink
Fix/is valid (react-hook-form#94)
Browse files Browse the repository at this point in the history
* fix isValid

* fix onBlur isValid issue

* code clean up

* error message update

* update the error message

* remove .vscode from the repo
  • Loading branch information
bluebill1049 authored Jun 21, 2019
1 parent 439840b commit c214d03
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 114 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ typings/
/coverage
.rpt2_cache
.coveralls.yml
.vscode
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-hook-form",
"version": "3.11.9",
"version": "3.11.10-beta.1",
"main": "dist/react-hook-form.js",
"module": "dist/react-hook-form.es.js",
"types": "dist/index.d.ts",
Expand Down
23 changes: 23 additions & 0 deletions src/global-modifying-module.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
interface Set<T> {
add(value: T): Set<T>;
clear(): void;
delete(value: T): boolean;
entries(): IterableIterator<[T, T]>;
forEach(
callbackfn: (value: T, index: T, set: Set<T>) => void,
thisArg?: any,
): void;
has(value: T): boolean;
keys(): IterableIterator<T>;
size: number;
values(): IterableIterator<T>;
[Symbol.iterator](): IterableIterator<T>;
[Symbol.toStringTag]: string;
}

interface SetConstructor {
new <T>(): Set<T>;
new <T>(iterable: Iterable<T>): Set<T>;
prototype: Set<any>;
}
declare var Set: SetConstructor;
38 changes: 36 additions & 2 deletions src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,18 @@ describe('useForm', () => {

it('should return false when field is not found', async () => {
hookForm.register({ type: 'input', name: 'test' });
expect(await hookForm.triggerValidation({ name: 'test', forceValidation:false })).toBeTruthy();
expect(
await hookForm.triggerValidation({
name: 'test',
forceValidation: false,
}),
).toBeTruthy();
});

it('should update value when value is supplied', async () => {
testComponent(() => {
hookForm = useForm({
mode: 'onChange'
mode: 'onChange',
});
return hookForm;
});
Expand Down Expand Up @@ -373,6 +378,35 @@ describe('useForm', () => {
});
});

describe('formState', () => {
it('should disable isValid for submit mode', () => {
expect(hookForm.formState.isValid).toBeUndefined();
});

it('should return false for onChange or onBlur mode by default', () => {
testComponent(() => {
hookForm = useForm({
mode: 'onBlur',
});
return hookForm;
});

expect(hookForm.formState.isValid).toBeFalsy();
});

it('should return true when no validation is registered', () => {
testComponent(() => {
hookForm = useForm({
mode: 'onBlur',
});
return hookForm;
});
hookForm.register({ type: 'text', name: 'test' });

expect(hookForm.formState.isValid).toBeFalsy();
});
});

describe('when component unMount', () => {
it('should call unSubscribe', () => {
hookForm.register({ type: 'text', name: 'test' });
Expand Down
87 changes: 51 additions & 36 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,17 @@ export default function useForm<Data extends DataType>(
const fieldsRef = useRef<FieldsObject<Data>>({});
const errorsRef = useRef<ErrorMessages<Data>>({});
const submitCountRef = useRef<number>(0);
const touchedFieldsRef = useRef<string[]>([]);
const touchedFieldsRef = useRef(new Set());
const watchFieldsRef = useRef<{ [key in keyof Data]?: boolean }>({});
const isWatchAllRef = useRef<boolean>(false);
const isSubmittingRef = useRef<boolean>(false);
const isSubmittedRef = useRef<boolean>(false);
const isDirtyRef = useRef<boolean>(false);
const reRenderForm = useState({})[1];
const validateAndStateUpdateRef = useRef<Function>();
const { isOnChange, isOnBlur, isOnSubmit } = modeChecker(mode);
const fieldsWithValidation = useRef(new Set());
const validFields = useRef(new Set());

const renderBaseOnError = (
name: keyof Data,
Expand All @@ -54,12 +57,18 @@ export default function useForm<Data extends DataType>(
): boolean => {
if (errors[name] && !error[name]) {
delete errorsRef.current[name];
validFields.current.add(name);
reRenderForm({});
return true;
} else if (error[name]) {
validFields.current.delete(name);
reRenderForm({});
return true;
}
if (!isOnSubmit && !validFields.current.has(name)) {
validFields.current.add(name);
reRenderForm({});
}
return false;
};

Expand All @@ -72,21 +81,17 @@ export default function useForm<Data extends DataType>(
if (!field) return;

if (shouldRender) {
if (!touchedFieldsRef.current.includes(name)) {
touchedFieldsRef.current.push(name);
}
touchedFieldsRef.current.add(name);
isDirtyRef.current = true;
}

const ref = field.ref;
const options = field.options;

if (isRadioInput(ref.type) && options) {
options.forEach(
({ ref: radioRef }): void => {
if (radioRef.value === value) radioRef.checked = true;
},
);
options.forEach(({ ref: radioRef }): void => {
if (radioRef.value === value) radioRef.checked = true;
});
return;
}

Expand All @@ -95,7 +100,7 @@ export default function useForm<Data extends DataType>(
};

const isValidateDisabled = <Name extends keyof Data>(): boolean =>
!isSubmittedRef.current && modeChecker(mode).isOnSubmit;
!isSubmittedRef.current && isOnSubmit;

const triggerValidation = async <Name extends keyof Data>({
name,
Expand Down Expand Up @@ -132,7 +137,6 @@ export default function useForm<Data extends DataType>(
const ref = fields[name];
if (!ref) return;
const isBlurType = type === 'blur';
const { isOnChange, isOnBlur } = modeChecker(mode);
const validateDisabled = isValidateDisabled();
const isWatchAll = isWatchAllRef.current;
const shouldUpdateWatchMode =
Expand All @@ -145,8 +149,8 @@ export default function useForm<Data extends DataType>(
shouldUpdateState = true;
}

if (!touchedFieldsRef.current.includes(name)) {
touchedFieldsRef.current.push(name);
if (!touchedFieldsRef.current.has(name)) {
touchedFieldsRef.current.add(name);
shouldUpdateState = true;
}

Expand Down Expand Up @@ -194,6 +198,7 @@ export default function useForm<Data extends DataType>(
null,
fieldsRef.current,
touchedFieldsRef,
fieldsWithValidation,
validateAndStateUpdateRef.current,
);

Expand Down Expand Up @@ -228,6 +233,15 @@ export default function useForm<Data extends DataType>(
if (elementRef && !elementRef.name) return warnMissingRef(elementRef);

const { name, type, value } = elementRef;

if (
!isOnSubmit &&
data &&
!isEmptyObject(data)
) {
fieldsWithValidation.current.add(name);
}

const { required = false, validate = undefined } = data || {};
const inputData = {
...data,
Expand Down Expand Up @@ -304,11 +318,9 @@ export default function useForm<Data extends DataType>(
if (typeof fieldNames === 'string') {
watchFields[fieldNames] = true;
} else if (Array.isArray(fieldNames)) {
fieldNames.forEach(
(name): void => {
watchFields[name] = true;
},
);
fieldNames.forEach((name): void => {
watchFields[name] = true;
});
} else {
isWatchAllRef.current = true;
watchFieldsRef.current = {};
Expand Down Expand Up @@ -419,21 +431,22 @@ export default function useForm<Data extends DataType>(
isWatchAllRef.current = false;
isSubmittedRef.current = false;
isDirtyRef.current = false;
touchedFieldsRef.current = [];
touchedFieldsRef.current = new Set();
fieldsWithValidation.current = new Set();
validFields.current = new Set();
submitCountRef.current = 0;
};

const unSubscribe = (): void => {
fieldsRef.current &&
Object.values(fieldsRef.current).forEach(
(field: Field): void => {
const { ref, options } = field;
isRadioInput(ref.type) && Array.isArray(options)
? options.forEach(
(fieldRef): void => removeEventListener(fieldRef, true),
)
: removeEventListener(field, true);
},
);
Object.values(fieldsRef.current).forEach((field: Field): void => {
const { ref, options } = field;
isRadioInput(ref.type) && Array.isArray(options)
? options.forEach((fieldRef): void =>
removeEventListener(fieldRef, true),
)
: removeEventListener(field, true);
});
fieldsRef.current = {};
resetRefs();
};
Expand All @@ -445,7 +458,7 @@ export default function useForm<Data extends DataType>(
.ref.closest('form')
.reset();
} catch {
console.warn(`⚠ No HTML input found, hence <form> look up failed.`);
console.warn(`⚠ Form element not found`);
}
resetRefs();
reRenderForm({});
Expand Down Expand Up @@ -476,13 +489,15 @@ export default function useForm<Data extends DataType>(
dirty: isDirtyRef.current,
isSubmitted: isSubmittedRef.current,
submitCount: submitCountRef.current,
touched: touchedFieldsRef.current,
touched: [...touchedFieldsRef.current],
isSubmitting: isSubmittingRef.current,
isValid:
touchedFieldsRef.current.length &&
touchedFieldsRef.current.length ===
Object.keys(fieldsRef.current).length &&
isEmptyObject(errorsRef.current),
...(!isOnSubmit
? {
isValid: fieldsWithValidation.current.size
? !isEmptyObject(fieldsRef.current) && validFields.current.size >= fieldsWithValidation.current.size
: !isEmptyObject(fieldsRef.current),
}
: null),
},
};
}
Loading

0 comments on commit c214d03

Please sign in to comment.