Repository: final-form/react-final-form
Pull request #47 (2017-12-08 13:49:37)

Added decorator support
Support for decorators, from [🏁 Final Form `v1.3.0`](https://github.com/final-form/final-form/releases/tag/v1.3.0).

codecov[bot]: # [Codecov](https://codecov.io/gh/final-form/react-final-form/pull/47?src=pr&el=h1) Report
> Merging [#47](https://codecov.io/gh/final-form/react-final-form/pull/47?src=pr&el=desc) into [master](https://codecov.io/gh/final-form/react-final-form/commit/c8eb275819d3f205184213617dbac81dbb8e8d3a?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/final-form/react-final-form/pull/47/graphs/tree.svg?width=650&token=s8VUe6IxMZ&height=150&src=pr)](https://codecov.io/gh/final-form/react-final-form/pull/47?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master    #47   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           9      9           
  Lines         143    148    +5     
  Branches       41     43    +2     
=====================================
+ Hits          143    148    +5
```


| [Impacted Files](https://codecov.io/gh/final-form/react-final-form/pull/47?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ReactFinalForm.js](https://codecov.io/gh/final-form/react-final-form/pull/47/diff?src=pr&el=tree#diff-c3JjL1JlYWN0RmluYWxGb3JtLmpz) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/final-form/react-final-form/pull/47?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/final-form/react-final-form/pull/47?src=pr&el=footer). Last update [c8eb275...337d155](https://codecov.io/gh/final-form/react-final-form/pull/47?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index e0ce6f6..20fe985 100644
--- a/README.md
+++ b/README.md
@@ -158,6 +158,7 @@ const MyForm = () => (
     * [`children?: ((props: FormRenderProps) => React.Node) | React.Node`](#children-props-formrenderprops--reactnode--reactnode)
     * [`component?: React.ComponentType<FormRenderProps>`](#component-reactcomponenttypeformrenderprops)
     * [`debug?: DebugFunction`](#debug-debugfunction)
+    * [`decorators?: Decorator[]`](#decorators-decorator)
     * [`initialValues?: Object`](#initialvalues-object)
     * [`mutators?: { [string]: Mutator }`](#mutators--string-mutator-)
     * [`onSubmit: (values: Object, callback: ?(errors: ?Object) => void) => ?Object | Promise<?Object> | void`](#onsubmit-values-object-callback-errors-object--void--object--promiseobject--void)
@@ -448,6 +449,11 @@ well as any non-API props passed into the `<Form/>` component.
 
 [See the 🏁 Final Form docs on `debug`](https://github.com/final-form/final-form#debug-debugfunction).
 
+#### `decorators?: Decorator[]`
+
+[`Decorator`](https://github.com/final-form/final-form#decorator-form-formapi--unsubscribe)s
+to apply to the form.
+
 #### `initialValues?: Object`
 
 [See the 🏁 Final Form docs on `initialValues`](https://github.com/final-form/final-form#initialvalues-object).
diff --git a/package.json b/package.json
index 5761cbb..8c68629 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-final-form",
-  "version": "1.1.1",
+  "version": "1.2.0",
   "description":
     "🏁 High performance subscription-based form state management for React",
   "main": "dist/react-final-form.cjs.js",
@@ -40,7 +40,7 @@
     "eslint-plugin-import": "^2.8.0",
     "eslint-plugin-jsx-a11y": "^6.0.2",
     "eslint-plugin-react": "^7.4.0",
-    "final-form": "^1.2.0",
+    "final-form": "^1.3.0",
     "flow": "^0.2.3",
     "flow-bin": "^0.60.1",
     "husky": "^0.14.3",
@@ -63,7 +63,7 @@
     "rollup-plugin-uglify": "^2.0.1"
   },
   "peerDependencies": {
-    "final-form": "^1.2.0",
+    "final-form": "^1.3.0",
     "prop-types": "^15.6.0",
     "react": "^15.0.0-0 || ^16.0.0-0"
   },
diff --git a/src/ReactFinalForm.js b/src/ReactFinalForm.js
index 39f3b54..bb4ee33 100644
--- a/src/ReactFinalForm.js
+++ b/src/ReactFinalForm.js
@@ -7,11 +7,17 @@ import {
   formSubscriptionItems,
   version as ffVersion
 } from 'final-form'
-import type { Api, Config, FormSubscription, FormState } from 'final-form'
+import type {
+  Api,
+  Config,
+  FormSubscription,
+  FormState,
+  Unsubscribe
+} from 'final-form'
 import type { FormProps as Props, ReactContext } from './types'
 import shallowEqual from './shallowEqual'
 import renderComponent from './renderComponent'
-export const version = '1.0.0'
+export const version = '1.2.0'
 
 export const all: FormSubscription = formSubscriptionItems.reduce(
   (result, key) => {
@@ -30,7 +36,7 @@ export default class ReactFinalForm extends React.PureComponent<Props, State> {
   props: Props
   state: State
   form: Api
-  unsubscribe: () => void
+  unsubscriptions: Unsubscribe[]
 
   static childContextTypes = {
     reactFinalForm: PropTypes.object
@@ -44,6 +50,7 @@ export default class ReactFinalForm extends React.PureComponent<Props, State> {
       children,
       component,
       debug,
+      decorators,
       initialValues,
       mutators,
       onSubmit,
@@ -70,16 +77,24 @@ export default class ReactFinalForm extends React.PureComponent<Props, State> {
       warning(false, e.message)
     }
     let initialState
-    this.unsubscribe =
-      this.form &&
-      this.form.subscribe((state: FormState) => {
-        if (initialState) {
-          this.notify(state)
-        } else {
-          initialState = state
-        }
-      }, subscription || all)
+    this.unsubscriptions = []
+    if (this.form) {
+      this.unsubscriptions.push(
+        this.form.subscribe((state: FormState) => {
+          if (initialState) {
+            this.notify(state)
+          } else {
+            initialState = state
+          }
+        }, subscription || all)
+      )
+    }
     this.state = { state: initialState }
+    if (decorators) {
+      decorators.forEach(decorator => {
+        this.unsubscriptions.push(decorator(this.form))
+      })
+    }
   }
 
   getChildContext() {
@@ -102,7 +117,7 @@ export default class ReactFinalForm extends React.PureComponent<Props, State> {
   }
 
   componentWillUnmount() {
-    this.unsubscribe()
+    this.unsubscriptions.forEach(unsubscribe => unsubscribe())
   }
 
   render() {
diff --git a/src/types.js.flow b/src/types.js.flow
index 49b3ad9..ee2f400 100644
--- a/src/types.js.flow
+++ b/src/types.js.flow
@@ -3,6 +3,7 @@ import * as React from 'react'
 import type {
   Api,
   Config,
+  Decorator,
   FormState,
   FormSubscription,
   FieldSubscription
@@ -55,7 +56,8 @@ export type RenderableProps<T> = $Shape<{
 }>
 
 export type FormProps = {
-  subscription?: FormSubscription
+  subscription?: FormSubscription,
+  decorators?: Decorator[]
 } & Config &
   RenderableProps<FormRenderProps>
 


Test patch:
diff --git a/src/ReactFinalForm.test.js b/src/ReactFinalForm.test.js
index 079ed5d..ada2a09 100644
--- a/src/ReactFinalForm.test.js
+++ b/src/ReactFinalForm.test.js
@@ -317,4 +317,42 @@ describe('ReactFinalForm', () => {
     expect(validate).toHaveBeenCalledTimes(4)
     expect(renderInput).toHaveBeenCalledTimes(2)
   })
+
+  it('should add decorators', () => {
+    const unsubscribe = jest.fn()
+    const decorator = jest.fn(() => unsubscribe)
+    class Container extends React.Component {
+      state = { shown: true }
+
+      render() {
+        return (
+          <div>
+            {this.state.shown && (
+              <Form
+                onSubmit={onSubmitMock}
+                render={() => <form />}
+                decorators={[decorator]}
+              />
+            )}
+            <button
+              type="button"
+              onClick={() => this.setState({ shown: false })}
+            >
+              Unmount
+            </button>
+          </div>
+        )
+      }
+    }
+    const dom = TestUtils.renderIntoDocument(<Container />)
+
+    expect(decorator).toHaveBeenCalled()
+    expect(decorator).toHaveBeenCalledTimes(1)
+    expect(unsubscribe).not.toHaveBeenCalled()
+
+    const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
+    TestUtils.Simulate.click(button)
+
+    expect(unsubscribe).toHaveBeenCalled()
+  })
 })