Skip to content

provide custom isEqual function #40

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

Merged
merged 2 commits into from
Oct 15, 2018
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
87 changes: 48 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,45 +81,50 @@ const MyForm = () => (

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

* [Examples](#examples)
* [Simple Example](#simple-example)
* [Rendering](#rendering)
* [API](#api)
* [`FieldArray : React.ComponentType<FieldArrayProps>`](#fieldarray--reactcomponenttypefieldarrayprops)
* [`version: string`](#version-string)
* [Types](#types)
* [`FieldArrayProps`](#fieldarrayprops)
* [`children?: ((props: FieldArrayRenderProps) => React.Node) | React.Node`](#children-props-fieldarrayrenderprops--reactnode--reactnode)
* [`component?: React.ComponentType<FieldArrayRenderProps>`](#component-reactcomponenttypefieldarrayrenderprops)
* [`name: string`](#name-string)
* [`render?: (props: FieldArrayRenderProps) => React.Node`](#render-props-fieldarrayrenderprops--reactnode)
* [`subscription?: FieldSubscription`](#subscription-fieldsubscription)
* [`validate?: (value: ?any[], allValues: Object) => ?any`](#validate-value-any-allvalues-object--any)
* [`FieldArrayRenderProps`](#fieldarrayrenderprops)
* [`fields.forEach: (iterator: (name: string, index: number) => void) => void`](#fieldsforeach-iterator-name-string-index-number--void--void)
* [`fields.insert: (index: number, value: any) => void`](#fieldsinsert-index-number-value-any--void)
* [`fields.map: (iterator: (name: string, index: number) => any) => any[]`](#fieldsmap-iterator-name-string-index-number--any--any)
* [`fields.move: (from: number, to: number) => void`](#fieldsmove-from-number-to-number--void)
* [`fields.name: string`](#fieldsname-string)
* [`fields.pop: () => any`](#fieldspop---any)
* [`fields.push: (value: any) => void`](#fieldspush-value-any--void)
* [`fields.remove: (index: number) => any`](#fieldsremove-index-number--any)
* [`fields.shift: () => any`](#fieldsshift---any)
* [`fields.swap: (indexA: number, indexB: number) => void`](#fieldsswap-indexa-number-indexb-number--void)
* [`fields.unshift: (value: any) => void`](#fieldsunshift-value-any--void)
* [`meta.active?: boolean`](#metaactive-boolean)
* [`meta.data: Object`](#metadata-object)
* [`meta.dirty?: boolean`](#metadirty-boolean)
* [`meta.error?: any`](#metaerror-any)
* [`meta.initial?: any`](#metainitial-any)
* [`meta.invalid?: boolean`](#metainvalid-boolean)
* [`meta.pristine?: boolean`](#metapristine-boolean)
* [`meta.submitError?: any`](#metasubmiterror-any)
* [`meta.submitFailed?: boolean`](#metasubmitfailed-boolean)
* [`meta.submitSucceeded?: boolean`](#metasubmitsucceeded-boolean)
* [`meta.touched?: boolean`](#metatouched-boolean)
* [`meta.valid?: boolean`](#metavalid-boolean)
* [`meta.visited?: boolean`](#metavisited-boolean)
- [🏁 React Final Form Arrays](#-react-final-form-arrays)
- [Installation](#installation)
- [Usage](#usage)
- [Table of Contents](#table-of-contents)
- [Examples](#examples)
- [Simple Example](#simple-example)
- [Rendering](#rendering)
- [API](#api)
- [`FieldArray : React.ComponentType<FieldArrayProps>`](#fieldarray--reactcomponenttypefieldarrayprops)
- [`version: string`](#version-string)
- [Types](#types)
- [`FieldArrayProps`](#fieldarrayprops)
- [`children?: ((props: FieldArrayRenderProps) => React.Node) | React.Node`](#children-props-fieldarrayrenderprops--reactnode--reactnode)
- [`component?: React.ComponentType<FieldArrayRenderProps>`](#component-reactcomponenttypefieldarrayrenderprops)
- [`name: string`](#name-string)
- [`render?: (props: FieldArrayRenderProps) => React.Node`](#render-props-fieldarrayrenderprops--reactnode)
- [`isEqual?: (allPreviousValues: Array<any>, allNewValues: Array<any>) => boolean`](#isequal-allpreviousvalues-arrayany-allnewvalues-arrayany--boolean)
- [`subscription?: FieldSubscription`](#subscription-fieldsubscription)
- [`validate?: (value: ?any[], allValues: Object) => ?any`](#validate-value-any-allvalues-object--any)
- [`FieldArrayRenderProps`](#fieldarrayrenderprops)
- [`fields.forEach: (iterator: (name: string, index: number) => void) => void`](#fieldsforeach-iterator-name-string-index-number--void--void)
- [`fields.insert: (index: number, value: any) => void`](#fieldsinsert-index-number-value-any--void)
- [`fields.map: (iterator: (name: string, index: number) => any) => any[]`](#fieldsmap-iterator-name-string-index-number--any--any)
- [`fields.move: (from: number, to: number) => void`](#fieldsmove-from-number-to-number--void)
- [`fields.name: string`](#fieldsname-string)
- [`fields.pop: () => any`](#fieldspop---any)
- [`fields.push: (value: any) => void`](#fieldspush-value-any--void)
- [`fields.remove: (index: number) => any`](#fieldsremove-index-number--any)
- [`fields.shift: () => any`](#fieldsshift---any)
- [`fields.swap: (indexA: number, indexB: number) => void`](#fieldsswap-indexa-number-indexb-number--void)
- [`fields.unshift: (value: any) => void`](#fieldsunshift-value-any--void)
- [`meta.active?: boolean`](#metaactive-boolean)
- [`meta.data: Object`](#metadata-object)
- [`meta.dirty?: boolean`](#metadirty-boolean)
- [`meta.error?: any`](#metaerror-any)
- [`meta.initial?: any`](#metainitial-any)
- [`meta.invalid?: boolean`](#metainvalid-boolean)
- [`meta.pristine?: boolean`](#metapristine-boolean)
- [`meta.submitError?: any`](#metasubmiterror-any)
- [`meta.submitFailed?: boolean`](#metasubmitfailed-boolean)
- [`meta.submitSucceeded?: boolean`](#metasubmitsucceeded-boolean)
- [`meta.touched?: boolean`](#metatouched-boolean)
- [`meta.valid?: boolean`](#metavalid-boolean)
- [`meta.visited?: boolean`](#metavisited-boolean)

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

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

#### `isEqual?: (allPreviousValues: Array<any>, allNewValues: Array<any>) => boolean`

A function that can be used to compare two arrays of values (before and after every change) and calculate pristine/dirty checks.

#### `subscription?: FieldSubscription`

A
Expand Down
16 changes: 12 additions & 4 deletions src/FieldArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ class FieldArray extends React.PureComponent<Props, State> {
this.mounted = false
}

isEqual = (a: Array<any>, b: Array<any>) => {
if (typeof this.props.isEqual === 'function') {
return this.props.isEqual(a, b)
}

return true
}

subscribe = (
{ name, subscription }: Props,
listener: (state: FieldState) => void
Expand All @@ -75,15 +83,16 @@ class FieldArray extends React.PureComponent<Props, State> {
listener,
subscription ? { ...subscription, length: true } : all,
{
getValidator: () => this.validate
getValidator: () => this.validate,
isEqual: this.isEqual
}
)
}

validate: FieldValidator = (...args) => {
const { validate } = this.props
if (!validate) return undefined
const error = validate(...args)
const error = validate(args[0], args[1])
if (!error || Array.isArray(error)) {
return error
} else {
Expand Down Expand Up @@ -191,8 +200,7 @@ class FieldArray extends React.PureComponent<Props, State> {
valid,
visited,
...fieldStateFunctions
} =
this.state.state || {}
} = this.state.state || {}
const meta = {
active,
dirty,
Expand Down
46 changes: 45 additions & 1 deletion src/FieldArray.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { Fragment } from 'react'
import TestUtils from 'react-dom/test-utils'
import { Form, Field } from 'react-final-form'
import arrayMutators from 'final-form-arrays'
Expand Down Expand Up @@ -505,4 +505,48 @@ describe('FieldArray', () => {
await sleep(2)
expect(spy).not.toHaveBeenCalled()
})

it('should provide default isEqual method to calculate pristine correctly', () => {
const arrayFieldRender = jest.fn(({ fields }) => (
<Fragment>
{fields.map((name, index) => (
<Fragment key={name}>
<Field name={`${name}.bar`} component="input" />
<button onClick={() => fields.remove(index)}>Remove</button>
</Fragment>
))}
</Fragment>
))

const formRender = jest.fn(() => (
<FieldArray name="foo" render={arrayFieldRender} />
))
const dom = TestUtils.renderIntoDocument(
<Form
initialValues={{ foo: [{ bar: 'example' }] }}
onSubmit={onSubmitMock}
mutators={arrayMutators}
render={formRender}
/>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')

// initially pristine true
expect(formRender.mock.calls[0][0]).toMatchObject({
pristine: true
})

// changing value, pristine false
TestUtils.Simulate.change(input, { target: { value: 'foo' } })
expect(formRender.mock.calls[1][0]).toMatchObject({ pristine: false })

// changing value back to default, pristine true
TestUtils.Simulate.change(input, { target: { value: 'example' } })
expect(formRender.mock.calls[2][0]).toMatchObject({ pristine: true })

// removing field, pristine false
TestUtils.Simulate.click(button)
expect(formRender.mock.calls[3][0]).toMatchObject({ pristine: false })
})
})