Skip to content

Commit

Permalink
Add innerRef prop to Field & FastField string components (#580)
Browse files Browse the repository at this point in the history
* Add innerRef to Field & FastField string components

* Add tests

* Update documentation
  • Loading branch information
prichodko authored and jaredpalmer committed Apr 12, 2018
1 parent b72382e commit 0cec09d
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 4 deletions.
5 changes: 5 additions & 0 deletions README.md
Expand Up @@ -368,6 +368,7 @@ npm install yup --save
- [`validationSchema?: Schema | (() => Schema)`](#validationschema-schema----schema)
- [`<Field />`](#field-)
- [`validate?: (value: any) => undefined | string | Promise<any>`](#validate-value-any--undefined--string--promiseany)
- [Refs](#refs)
- [`<FieldArray/>`](#fieldarray)
- [`name: string`](#name-string)
- [`validateOnChange?: boolean`](#validateonchange-boolean-1)
Expand Down Expand Up @@ -1322,6 +1323,10 @@ const validate = value => {
Note: To allow for i18n libraries, the TypeScript typings for `validate` are
slightly relaxed and allow you to return a `Function` (e.g. `i18n('invalid')`).

#### Refs

When you are **not** using a custom component and you need to access the underlying DOM node created by `Field` (e.g. to call `focus`), pass the callback to the `innerRef` prop instead.

### `<FieldArray/>`

`<FieldArray />` is a component that helps with common array/list manipulations. You pass it a `name` property with the path to the key within `values` that holds the relevant array. `<FieldArray />` will then give you access to array helper methods via render props. For convenience, calling these methods will trigger validation and also manage `touched` for you.
Expand Down
5 changes: 4 additions & 1 deletion src/FastField.tsx
Expand Up @@ -33,6 +33,7 @@ export class FastField<
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
validate: PropTypes.func,
innerRef: PropTypes.func,
};

reset: Function;
Expand Down Expand Up @@ -292,9 +293,11 @@ export class FastField<
}

if (typeof component === 'string') {
const { innerRef, ...rest } = props;
return React.createElement(component as any, {
ref: innerRef,
...field,
...props,
...rest,
children,
});
}
Expand Down
8 changes: 7 additions & 1 deletion src/Field.tsx
Expand Up @@ -73,6 +73,9 @@ export interface FieldConfig {

/** Field value */
value?: any;

/** Inner ref */
innerRef?: (instance: any) => void;
}

export type FieldAttributes = GenericFieldHTMLAttributes & FieldConfig;
Expand All @@ -96,6 +99,7 @@ export class Field<Props extends FieldAttributes = any> extends React.Component<
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
validate: PropTypes.func,
innerRef: PropTypes.func,
};

componentWillMount() {
Expand Down Expand Up @@ -181,9 +185,11 @@ export class Field<Props extends FieldAttributes = any> extends React.Component<
}

if (typeof component === 'string') {
const { innerRef, ...rest } = props;
return React.createElement(component as any, {
ref: innerRef,
...field,
...props,
...rest,
children,
});
}
Expand Down
33 changes: 32 additions & 1 deletion test/FastField.test.tsx
Expand Up @@ -3,7 +3,7 @@ import * as ReactDOM from 'react-dom';

import { FastField as Field, FieldProps, Formik, FormikProps } from '../src';

import { shallow } from 'enzyme';
import { shallow, mount } from 'enzyme';
import { noop } from './testHelpers';

interface TestFormValues {
Expand Down Expand Up @@ -170,6 +170,37 @@ describe('A <Field />', () => {
expect(actual.field.value).toBe('jared');
expect(actual.form).toEqual(injected);
});

it('assigns innerRef as a ref to string components', () => {
const innerRef = jest.fn();
const tree = mount(<Field name="name" innerRef={innerRef} />, {
context: {
formik: {
registerField: jest.fn(noop),
},
},
});
const element = tree.find('input').instance();
expect(innerRef).toHaveBeenCalledWith(element);
});

it('forwards innerRef to React component', () => {
let actual: any; /** FieldProps ;) */
const Component: React.SFC<FieldProps> = props =>
(actual = props) && null;

const innerRef = jest.fn();

ReactDOM.render(
<TestForm
render={() => (
<Field name="name" component={Component} innerRef={innerRef} />
)}
/>,
node
);
expect(actual.innerRef).toBe(innerRef);
});
});

describe('<Field render />', () => {
Expand Down
30 changes: 29 additions & 1 deletion test/Field.test.tsx
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Formik, Field, FieldProps, FormikProps } from '../src';

import { shallow } from 'enzyme';
import { shallow, mount } from 'enzyme';
import { noop } from './testHelpers';

interface TestFormValues {
Expand Down Expand Up @@ -90,6 +90,7 @@ describe('A <Field />', () => {
value: 'ian',
},
});

expect(handleBlur).toHaveBeenCalled();
expect(setFieldError).toHaveBeenCalled();
expect(validate).toHaveBeenCalled();
Expand Down Expand Up @@ -179,6 +180,33 @@ describe('A <Field />', () => {
expect(actual.field.onBlur).toBe(handleBlur);
expect(actual.form).toEqual(injected);
});

it('assigns innerRef as a ref to string components', () => {
const innerRef = jest.fn();
const tree = mount(<Field name="name" innerRef={innerRef} />, {
context: { formik: {} },
});
const element = tree.find('input').instance();
expect(innerRef).toHaveBeenCalledWith(element);
});

it('forwards innerRef to React component', () => {
let actual: any; /** FieldProps ;) */
const Component: React.SFC<FieldProps> = props =>
(actual = props) && null;

const innerRef = jest.fn();

ReactDOM.render(
<TestForm
render={() => (
<Field name="name" component={Component} innerRef={innerRef} />
)}
/>,
node
);
expect(actual.innerRef).toBe(innerRef);
});
});

describe('<Field render />', () => {
Expand Down

0 comments on commit 0cec09d

Please sign in to comment.