diff --git a/docs/src/api/useField.mdx b/docs/src/api/useField.mdx
index 16b14eef..026eff58 100644
--- a/docs/src/api/useField.mdx
+++ b/docs/src/api/useField.mdx
@@ -4,24 +4,23 @@ menu: Api
route: /api/usefield
---
-
# useField
_Hook to mount a field to the form._
```tsx
-import { useField } from "fielder"
+import { useField } from 'fielder';
```
## Example usage
```tsx
const [passwordProps, passwordMeta] = useField({
- name: "password",
- validate: passwordValidation,
-})
+ name: 'password',
+ validate: passwordValidation
+});
-return
+return ;
```
## Return type
@@ -66,7 +65,9 @@ _Whether the initial value should be considered valid._
Type: `boolean`
-Default: `false`
+Default: `false` (see comment below)
+
+> When no `validate` argument is passed to `useField`, the default value is `true`
### initialError
@@ -132,11 +133,11 @@ Props which can be passed to a form element / component.
```ts
type UseFieldProps = {
- readonly name: string
- readonly value: T
- readonly onChange: ChangeEventHandler
- readonly onBlur: () => void
-}
+ readonly name: string;
+ readonly value: T;
+ readonly onChange: ChangeEventHandler;
+ readonly onBlur: () => void;
+};
```
### UseFieldMeta
@@ -145,9 +146,9 @@ Additional information about a field and it's validation state.
```ts
type UseFieldMeta = {
- readonly touched: boolean
- readonly error?: Error | string
- readonly isValid: boolean
- readonly isValidating: boolean
-}
+ readonly touched: boolean;
+ readonly error?: Error | string;
+ readonly isValid: boolean;
+ readonly isValidating: boolean;
+};
```
diff --git a/src/useField.spec.tsx b/src/useField.spec.tsx
index 270b8bf4..714b3f6e 100644
--- a/src/useField.spec.tsx
+++ b/src/useField.spec.tsx
@@ -1,8 +1,8 @@
-import React, { FC } from "react";
-import { mount } from "enzyme";
-import { useField, UseFieldResponse } from "./useField";
-import { FielderContext } from "./context";
-import { FieldConfig } from "./types";
+import React, { FC } from 'react';
+import { mount } from 'enzyme';
+import { useField, UseFieldResponse } from './useField';
+import { FielderContext } from './context';
+import { FieldConfig } from './types';
const context = {
fields: {},
@@ -30,16 +30,16 @@ const Fixture: FC = ({ children }) => {
beforeEach(jest.clearAllMocks);
-describe("on mount", () => {
- it("calls mountField", () => {
- args = { name: "someField" };
+describe('on mount', () => {
+ it('calls mountField with default values', () => {
+ args = { name: 'someField' };
mount();
expect(context.mountField).toBeCalledTimes(1);
expect(context.mountField).toBeCalledWith({
name: args.name,
initialError: undefined,
- initialValid: false,
+ initialValid: true,
initialValue: undefined,
initialTouched: false,
validate: undefined,
@@ -49,12 +49,30 @@ describe("on mount", () => {
});
});
- it("calls mountField with default overrides", () => {
+ it('calls mountField with default values (validate function provided)', () => {
+ args = { name: 'someField', validate: jest.fn() };
+ mount();
+
+ expect(context.mountField).toBeCalledTimes(1);
+ expect(context.mountField).toBeCalledWith({
+ name: args.name,
+ initialError: undefined,
+ initialValid: false,
+ initialValue: undefined,
+ initialTouched: false,
+ validate: args.validate,
+ validateOnBlur: true,
+ validateOnChange: true,
+ validateOnUpdate: false
+ });
+ });
+
+ it('calls mountField with default overrides', () => {
args = {
- name: "someField",
- initialError: "aaa",
+ name: 'someField',
+ initialError: 'aaa',
initialValid: true,
- initialValue: "hello",
+ initialValue: 'hello',
initialTouched: true,
validate: jest.fn(),
validateOnBlur: false,
@@ -70,9 +88,9 @@ describe("on mount", () => {
});
});
-describe("on unmount", () => {
- it("calls unmountField", () => {
- args = { name: "someField" };
+describe('on unmount', () => {
+ it('calls unmountField', () => {
+ args = { name: 'someField' };
const wrapper = mount();
wrapper.unmount();
@@ -83,8 +101,8 @@ describe("on unmount", () => {
});
});
- it("calls unmountField and destroys value", () => {
- args = { name: "someField", destroyOnUnmount: true };
+ it('calls unmountField and destroys value', () => {
+ args = { name: 'someField', destroyOnUnmount: true };
const wrapper = mount();
wrapper.unmount();
@@ -96,9 +114,9 @@ describe("on unmount", () => {
});
});
-describe("on blur", () => {
- it("calls blurField", () => {
- args = { name: "someField" };
+describe('on blur', () => {
+ it('calls blurField', () => {
+ args = { name: 'someField' };
mount();
response[0].onBlur();
@@ -109,16 +127,16 @@ describe("on blur", () => {
});
});
-describe("on change", () => {
- describe("basic input", () => {
- it("calls setFieldValue", () => {
- const value = "newval";
- args = { name: "someField" };
+describe('on change', () => {
+ describe('basic input', () => {
+ it('calls setFieldValue', () => {
+ const value = 'newval';
+ args = { name: 'someField' };
mount();
response[0].onChange({
currentTarget: {
- tagName: "INPUT",
+ tagName: 'INPUT',
getAttribute: () => undefined,
value
}
@@ -132,16 +150,16 @@ describe("on change", () => {
});
});
- describe("radio input", () => {
- it("calls setFieldValue", () => {
- const value = "newval";
- args = { name: "someField" };
+ describe('radio input', () => {
+ it('calls setFieldValue', () => {
+ const value = 'newval';
+ args = { name: 'someField' };
mount();
response[0].onChange({
currentTarget: {
- tagName: "INPUT",
- getAttribute: () => "radio",
+ tagName: 'INPUT',
+ getAttribute: () => 'radio',
value
}
} as any);
@@ -154,22 +172,22 @@ describe("on change", () => {
});
});
- describe("checkbox input", () => {
- const value = "newval";
- args = { name: "someField" };
+ describe('checkbox input', () => {
+ const value = 'newval';
+ args = { name: 'someField' };
beforeEach(() => {
mount();
response[0].onChange({
currentTarget: {
- tagName: "INPUT",
- getAttribute: () => "checkbox",
+ tagName: 'INPUT',
+ getAttribute: () => 'checkbox',
value
}
} as any);
});
- it("calls setFieldValue", () => {
+ it('calls setFieldValue', () => {
expect(context.setFieldValue).toBeCalledTimes(1);
expect(context.setFieldValue).toBeCalledWith(
expect.objectContaining({
@@ -178,37 +196,37 @@ describe("on change", () => {
);
});
- describe("on setFieldValue value function", () => {
+ describe('on setFieldValue value function', () => {
let valueFn: Function;
- const otherVals = ["other", "again"];
+ const otherVals = ['other', 'again'];
beforeEach(() => {
valueFn = context.setFieldValue.mock.calls[0][0].value;
});
- it("instantiates array with value", () => {
+ it('instantiates array with value', () => {
expect(valueFn()).toEqual([value]);
});
- it("removes value from array", () => {
+ it('removes value from array', () => {
expect(valueFn([...otherVals, value])).toEqual(otherVals);
});
- it("appends value to array", () => {
+ it('appends value to array', () => {
expect(valueFn(otherVals)).toEqual([...otherVals, value]);
});
});
});
- describe("select input", () => {
- it("calls setFieldValue", () => {
- const value = "newval";
- args = { name: "someField" };
+ describe('select input', () => {
+ it('calls setFieldValue', () => {
+ const value = 'newval';
+ args = { name: 'someField' };
mount();
response[0].onChange({
currentTarget: {
- tagName: "SELECT",
+ tagName: 'SELECT',
value,
getAttribute: () => undefined
}
diff --git a/src/useField.ts b/src/useField.ts
index 082fbda3..e4a19081 100644
--- a/src/useField.ts
+++ b/src/useField.ts
@@ -7,9 +7,9 @@ import React, {
useCallback,
useMemo,
useLayoutEffect
-} from "react";
-import { FielderContext } from "./context";
-import { FormState, FormError, FieldState, FieldConfig } from "./types";
+} from 'react';
+import { FielderContext } from './context';
+import { FormState, FormError, FieldState, FieldConfig } from './types';
export type UseFieldProps = {
readonly name: string;
@@ -19,10 +19,10 @@ export type UseFieldProps = {
};
export type UseFieldMeta = {
- readonly touched: FieldState["touched"];
- readonly error: FieldState["error"];
- readonly isValid: FieldState["isValid"];
- readonly isValidating: FieldState["isValidating"];
+ readonly touched: FieldState['touched'];
+ readonly error: FieldState['error'];
+ readonly isValid: FieldState['isValid'];
+ readonly isValidating: FieldState['isValidating'];
};
type SupportedElements =
@@ -36,7 +36,7 @@ export type UseFieldResponse = [UseFieldProps, UseFieldMeta];
export const useField = ({
name: initialName,
validate,
- initialValid = false,
+ initialValid = !validate,
initialError = undefined,
initialValue = undefined,
initialTouched = false,
@@ -68,7 +68,7 @@ export const useField = ({
useLayoutEffect(() => {
if (fields[name] && fields[name]._isActive) {
- throw Error("Duplicate field mounted.");
+ throw Error('Duplicate field mounted.');
}
mountField({
@@ -95,7 +95,7 @@ export const useField = ({
const type = getElementType(e);
const newVal = e.currentTarget.value;
- if (type !== "checkbox") {
+ if (type !== 'checkbox') {
return setFieldValue({ name, value: newVal });
}
@@ -130,23 +130,23 @@ const getElementType = (e: ChangeEvent) => {
const target = e.currentTarget || e.target;
const tagName = target.tagName.toLowerCase();
- if (tagName === "select") {
- return "select";
+ if (tagName === 'select') {
+ return 'select';
}
- const type = target.getAttribute("type");
+ const type = target.getAttribute('type');
- if (tagName === "input" && type === "checkbox") {
- return "checkbox";
+ if (tagName === 'input' && type === 'checkbox') {
+ return 'checkbox';
}
- if (tagName === "input" && type === "radio") {
- return "radio";
+ if (tagName === 'input' && type === 'radio') {
+ return 'radio';
}
- if (tagName === "input") {
- return "input";
+ if (tagName === 'input') {
+ return 'input';
}
- throw Error("Unsupported input element");
+ throw Error('Unsupported input element');
};