Skip to content

Commit 5019141

Browse files
maciejmyslinskierikras
authored andcommitted
provide custom isEqual function (#40)
* provide custom isEqual function * Add isEqual prop to FieldArray component
1 parent 784cd77 commit 5019141

File tree

3 files changed

+105
-44
lines changed

3 files changed

+105
-44
lines changed

README.md

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -81,45 +81,50 @@ const MyForm = () => (
8181

8282
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
8383

84-
* [Examples](#examples)
85-
* [Simple Example](#simple-example)
86-
* [Rendering](#rendering)
87-
* [API](#api)
88-
* [`FieldArray : React.ComponentType<FieldArrayProps>`](#fieldarray--reactcomponenttypefieldarrayprops)
89-
* [`version: string`](#version-string)
90-
* [Types](#types)
91-
* [`FieldArrayProps`](#fieldarrayprops)
92-
* [`children?: ((props: FieldArrayRenderProps) => React.Node) | React.Node`](#children-props-fieldarrayrenderprops--reactnode--reactnode)
93-
* [`component?: React.ComponentType<FieldArrayRenderProps>`](#component-reactcomponenttypefieldarrayrenderprops)
94-
* [`name: string`](#name-string)
95-
* [`render?: (props: FieldArrayRenderProps) => React.Node`](#render-props-fieldarrayrenderprops--reactnode)
96-
* [`subscription?: FieldSubscription`](#subscription-fieldsubscription)
97-
* [`validate?: (value: ?any[], allValues: Object) => ?any`](#validate-value-any-allvalues-object--any)
98-
* [`FieldArrayRenderProps`](#fieldarrayrenderprops)
99-
* [`fields.forEach: (iterator: (name: string, index: number) => void) => void`](#fieldsforeach-iterator-name-string-index-number--void--void)
100-
* [`fields.insert: (index: number, value: any) => void`](#fieldsinsert-index-number-value-any--void)
101-
* [`fields.map: (iterator: (name: string, index: number) => any) => any[]`](#fieldsmap-iterator-name-string-index-number--any--any)
102-
* [`fields.move: (from: number, to: number) => void`](#fieldsmove-from-number-to-number--void)
103-
* [`fields.name: string`](#fieldsname-string)
104-
* [`fields.pop: () => any`](#fieldspop---any)
105-
* [`fields.push: (value: any) => void`](#fieldspush-value-any--void)
106-
* [`fields.remove: (index: number) => any`](#fieldsremove-index-number--any)
107-
* [`fields.shift: () => any`](#fieldsshift---any)
108-
* [`fields.swap: (indexA: number, indexB: number) => void`](#fieldsswap-indexa-number-indexb-number--void)
109-
* [`fields.unshift: (value: any) => void`](#fieldsunshift-value-any--void)
110-
* [`meta.active?: boolean`](#metaactive-boolean)
111-
* [`meta.data: Object`](#metadata-object)
112-
* [`meta.dirty?: boolean`](#metadirty-boolean)
113-
* [`meta.error?: any`](#metaerror-any)
114-
* [`meta.initial?: any`](#metainitial-any)
115-
* [`meta.invalid?: boolean`](#metainvalid-boolean)
116-
* [`meta.pristine?: boolean`](#metapristine-boolean)
117-
* [`meta.submitError?: any`](#metasubmiterror-any)
118-
* [`meta.submitFailed?: boolean`](#metasubmitfailed-boolean)
119-
* [`meta.submitSucceeded?: boolean`](#metasubmitsucceeded-boolean)
120-
* [`meta.touched?: boolean`](#metatouched-boolean)
121-
* [`meta.valid?: boolean`](#metavalid-boolean)
122-
* [`meta.visited?: boolean`](#metavisited-boolean)
84+
- [🏁 React Final Form Arrays](#-react-final-form-arrays)
85+
- [Installation](#installation)
86+
- [Usage](#usage)
87+
- [Table of Contents](#table-of-contents)
88+
- [Examples](#examples)
89+
- [Simple Example](#simple-example)
90+
- [Rendering](#rendering)
91+
- [API](#api)
92+
- [`FieldArray : React.ComponentType<FieldArrayProps>`](#fieldarray--reactcomponenttypefieldarrayprops)
93+
- [`version: string`](#version-string)
94+
- [Types](#types)
95+
- [`FieldArrayProps`](#fieldarrayprops)
96+
- [`children?: ((props: FieldArrayRenderProps) => React.Node) | React.Node`](#children-props-fieldarrayrenderprops--reactnode--reactnode)
97+
- [`component?: React.ComponentType<FieldArrayRenderProps>`](#component-reactcomponenttypefieldarrayrenderprops)
98+
- [`name: string`](#name-string)
99+
- [`render?: (props: FieldArrayRenderProps) => React.Node`](#render-props-fieldarrayrenderprops--reactnode)
100+
- [`isEqual?: (allPreviousValues: Array<any>, allNewValues: Array<any>) => boolean`](#isequal-allpreviousvalues-arrayany-allnewvalues-arrayany--boolean)
101+
- [`subscription?: FieldSubscription`](#subscription-fieldsubscription)
102+
- [`validate?: (value: ?any[], allValues: Object) => ?any`](#validate-value-any-allvalues-object--any)
103+
- [`FieldArrayRenderProps`](#fieldarrayrenderprops)
104+
- [`fields.forEach: (iterator: (name: string, index: number) => void) => void`](#fieldsforeach-iterator-name-string-index-number--void--void)
105+
- [`fields.insert: (index: number, value: any) => void`](#fieldsinsert-index-number-value-any--void)
106+
- [`fields.map: (iterator: (name: string, index: number) => any) => any[]`](#fieldsmap-iterator-name-string-index-number--any--any)
107+
- [`fields.move: (from: number, to: number) => void`](#fieldsmove-from-number-to-number--void)
108+
- [`fields.name: string`](#fieldsname-string)
109+
- [`fields.pop: () => any`](#fieldspop---any)
110+
- [`fields.push: (value: any) => void`](#fieldspush-value-any--void)
111+
- [`fields.remove: (index: number) => any`](#fieldsremove-index-number--any)
112+
- [`fields.shift: () => any`](#fieldsshift---any)
113+
- [`fields.swap: (indexA: number, indexB: number) => void`](#fieldsswap-indexa-number-indexb-number--void)
114+
- [`fields.unshift: (value: any) => void`](#fieldsunshift-value-any--void)
115+
- [`meta.active?: boolean`](#metaactive-boolean)
116+
- [`meta.data: Object`](#metadata-object)
117+
- [`meta.dirty?: boolean`](#metadirty-boolean)
118+
- [`meta.error?: any`](#metaerror-any)
119+
- [`meta.initial?: any`](#metainitial-any)
120+
- [`meta.invalid?: boolean`](#metainvalid-boolean)
121+
- [`meta.pristine?: boolean`](#metapristine-boolean)
122+
- [`meta.submitError?: any`](#metasubmiterror-any)
123+
- [`meta.submitFailed?: boolean`](#metasubmitfailed-boolean)
124+
- [`meta.submitSucceeded?: boolean`](#metasubmitsucceeded-boolean)
125+
- [`meta.touched?: boolean`](#metatouched-boolean)
126+
- [`meta.valid?: boolean`](#metavalid-boolean)
127+
- [`meta.visited?: boolean`](#metavisited-boolean)
123128

124129
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
125130

@@ -184,6 +189,10 @@ A render function that is given
184189
[`FieldArrayRenderProps`](#fieldarrayrenderprops), as well as any non-API props
185190
passed into the `<FieldArray/>` component.
186191

192+
#### `isEqual?: (allPreviousValues: Array<any>, allNewValues: Array<any>) => boolean`
193+
194+
A function that can be used to compare two arrays of values (before and after every change) and calculate pristine/dirty checks.
195+
187196
#### `subscription?: FieldSubscription`
188197

189198
A

src/FieldArray.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ class FieldArray extends React.Component<Props, State> {
6666
this.mounted = false
6767
}
6868

69+
isEqual = (a: Array<any>, b: Array<any>) => {
70+
if (typeof this.props.isEqual === 'function') {
71+
return this.props.isEqual(a, b)
72+
}
73+
74+
return true
75+
}
76+
6977
subscribe = (
7078
{ name, subscription }: Props,
7179
listener: (state: FieldState) => void
@@ -75,15 +83,16 @@ class FieldArray extends React.Component<Props, State> {
7583
listener,
7684
subscription ? { ...subscription, length: true } : all,
7785
{
78-
getValidator: () => this.validate
86+
getValidator: () => this.validate,
87+
isEqual: this.isEqual
7988
}
8089
)
8190
}
8291

8392
validate: FieldValidator = (...args) => {
8493
const { validate } = this.props
8594
if (!validate) return undefined
86-
const error = validate(...args)
95+
const error = validate(args[0], args[1])
8796
if (!error || Array.isArray(error)) {
8897
return error
8998
} else {
@@ -191,8 +200,7 @@ class FieldArray extends React.Component<Props, State> {
191200
valid,
192201
visited,
193202
...fieldStateFunctions
194-
} =
195-
this.state.state || {}
203+
} = this.state.state || {}
196204
const meta = {
197205
active,
198206
dirty,

src/FieldArray.test.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, { Fragment } from 'react'
22
import TestUtils from 'react-dom/test-utils'
33
import { Form, Field } from 'react-final-form'
44
import arrayMutators from 'final-form-arrays'
@@ -505,4 +505,48 @@ describe('FieldArray', () => {
505505
await sleep(2)
506506
expect(spy).not.toHaveBeenCalled()
507507
})
508+
509+
it('should provide default isEqual method to calculate pristine correctly', () => {
510+
const arrayFieldRender = jest.fn(({ fields }) => (
511+
<Fragment>
512+
{fields.map((name, index) => (
513+
<Fragment key={name}>
514+
<Field name={`${name}.bar`} component="input" />
515+
<button onClick={() => fields.remove(index)}>Remove</button>
516+
</Fragment>
517+
))}
518+
</Fragment>
519+
))
520+
521+
const formRender = jest.fn(() => (
522+
<FieldArray name="foo" render={arrayFieldRender} />
523+
))
524+
const dom = TestUtils.renderIntoDocument(
525+
<Form
526+
initialValues={{ foo: [{ bar: 'example' }] }}
527+
onSubmit={onSubmitMock}
528+
mutators={arrayMutators}
529+
render={formRender}
530+
/>
531+
)
532+
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
533+
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
534+
535+
// initially pristine true
536+
expect(formRender.mock.calls[0][0]).toMatchObject({
537+
pristine: true
538+
})
539+
540+
// changing value, pristine false
541+
TestUtils.Simulate.change(input, { target: { value: 'foo' } })
542+
expect(formRender.mock.calls[1][0]).toMatchObject({ pristine: false })
543+
544+
// changing value back to default, pristine true
545+
TestUtils.Simulate.change(input, { target: { value: 'example' } })
546+
expect(formRender.mock.calls[2][0]).toMatchObject({ pristine: true })
547+
548+
// removing field, pristine false
549+
TestUtils.Simulate.click(button)
550+
expect(formRender.mock.calls[3][0]).toMatchObject({ pristine: false })
551+
})
508552
})

0 commit comments

Comments
 (0)