Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default to valid when no validation is set #15

Merged
merged 1 commit into from
Jan 9, 2020
Merged
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
35 changes: 18 additions & 17 deletions docs/src/api/useField.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <input type={"password"} {...passwordProps} />
return <input type={'password'} {...passwordProps} />;
```

## Return type
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -132,11 +133,11 @@ Props which can be passed to a form element / component.

```ts
type UseFieldProps<T = any> = {
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
Expand All @@ -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;
};
```
116 changes: 67 additions & 49 deletions src/useField.spec.tsx
Original file line number Diff line number Diff line change
@@ -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: {},
Expand Down Expand Up @@ -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(<Fixture />);

expect(context.mountField).toBeCalledTimes(1);
expect(context.mountField).toBeCalledWith({
name: args.name,
initialError: undefined,
initialValid: false,
initialValid: true,
initialValue: undefined,
initialTouched: false,
validate: undefined,
Expand All @@ -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(<Fixture />);

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,
Expand All @@ -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(<Fixture />);
wrapper.unmount();

Expand All @@ -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(<Fixture />);
wrapper.unmount();

Expand All @@ -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(<Fixture />);
response[0].onBlur();

Expand All @@ -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(<Fixture />);
response[0].onChange({
currentTarget: {
tagName: "INPUT",
tagName: 'INPUT',
getAttribute: () => undefined,
value
}
Expand All @@ -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(<Fixture />);
response[0].onChange({
currentTarget: {
tagName: "INPUT",
getAttribute: () => "radio",
tagName: 'INPUT',
getAttribute: () => 'radio',
value
}
} as any);
Expand All @@ -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(<Fixture />);
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({
Expand All @@ -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(<Fixture />);
response[0].onChange({
currentTarget: {
tagName: "SELECT",
tagName: 'SELECT',
value,
getAttribute: () => undefined
}
Expand Down
40 changes: 20 additions & 20 deletions src/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = any> = {
readonly name: string;
Expand All @@ -19,10 +19,10 @@ export type UseFieldProps<T = any> = {
};

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 =
Expand All @@ -36,7 +36,7 @@ export type UseFieldResponse = [UseFieldProps, UseFieldMeta];
export const useField = <T = any>({
name: initialName,
validate,
initialValid = false,
initialValid = !validate,
initialError = undefined,
initialValue = undefined,
initialTouched = false,
Expand Down Expand Up @@ -68,7 +68,7 @@ export const useField = <T = any>({

useLayoutEffect(() => {
if (fields[name] && fields[name]._isActive) {
throw Error("Duplicate field mounted.");
throw Error('Duplicate field mounted.');
}

mountField({
Expand All @@ -95,7 +95,7 @@ export const useField = <T = any>({
const type = getElementType(e);
const newVal = e.currentTarget.value;

if (type !== "checkbox") {
if (type !== 'checkbox') {
return setFieldValue({ name, value: newVal });
}

Expand Down Expand Up @@ -130,23 +130,23 @@ const getElementType = (e: ChangeEvent<SupportedElements>) => {
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');
};