From 0cec09d1ca6ea715719c337556a0484244c60219 Mon Sep 17 00:00:00 2001 From: Pavel Prichodko Date: Thu, 12 Apr 2018 15:43:36 +0200 Subject: [PATCH] Add innerRef prop to Field & FastField string components (#580) * Add innerRef to Field & FastField string components * Add tests * Update documentation --- README.md | 5 +++++ src/FastField.tsx | 5 ++++- src/Field.tsx | 8 +++++++- test/FastField.test.tsx | 33 ++++++++++++++++++++++++++++++++- test/Field.test.tsx | 30 +++++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c15502cb9..1491051eb 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,7 @@ npm install yup --save - [`validationSchema?: Schema | (() => Schema)`](#validationschema-schema----schema) - [``](#field-) - [`validate?: (value: any) => undefined | string | Promise`](#validate-value-any--undefined--string--promiseany) + - [Refs](#refs) - [``](#fieldarray) - [`name: string`](#name-string) - [`validateOnChange?: boolean`](#validateonchange-boolean-1) @@ -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. + ### `` `` 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. `` 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. diff --git a/src/FastField.tsx b/src/FastField.tsx index 06f3f2ec2..3c85e7a1d 100644 --- a/src/FastField.tsx +++ b/src/FastField.tsx @@ -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; @@ -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, }); } diff --git a/src/Field.tsx b/src/Field.tsx index 05af2c5ec..7782bb442 100644 --- a/src/Field.tsx +++ b/src/Field.tsx @@ -73,6 +73,9 @@ export interface FieldConfig { /** Field value */ value?: any; + + /** Inner ref */ + innerRef?: (instance: any) => void; } export type FieldAttributes = GenericFieldHTMLAttributes & FieldConfig; @@ -96,6 +99,7 @@ export class Field extends React.Component< render: PropTypes.func, children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), validate: PropTypes.func, + innerRef: PropTypes.func, }; componentWillMount() { @@ -181,9 +185,11 @@ export class Field extends React.Component< } if (typeof component === 'string') { + const { innerRef, ...rest } = props; return React.createElement(component as any, { + ref: innerRef, ...field, - ...props, + ...rest, children, }); } diff --git a/test/FastField.test.tsx b/test/FastField.test.tsx index 5d8e9b5f8..de74bd697 100644 --- a/test/FastField.test.tsx +++ b/test/FastField.test.tsx @@ -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 { @@ -170,6 +170,37 @@ describe('A ', () => { 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(, { + 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 = props => + (actual = props) && null; + + const innerRef = jest.fn(); + + ReactDOM.render( + ( + + )} + />, + node + ); + expect(actual.innerRef).toBe(innerRef); + }); }); describe('', () => { diff --git a/test/Field.test.tsx b/test/Field.test.tsx index 100aa8183..9b47df036 100644 --- a/test/Field.test.tsx +++ b/test/Field.test.tsx @@ -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 { @@ -90,6 +90,7 @@ describe('A ', () => { value: 'ian', }, }); + expect(handleBlur).toHaveBeenCalled(); expect(setFieldError).toHaveBeenCalled(); expect(validate).toHaveBeenCalled(); @@ -179,6 +180,33 @@ describe('A ', () => { 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(, { + 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 = props => + (actual = props) && null; + + const innerRef = jest.fn(); + + ReactDOM.render( + ( + + )} + />, + node + ); + expect(actual.innerRef).toBe(innerRef); + }); }); describe('', () => {