Repository: final-form/react-final-form
Pull request #498 (2019-05-27 09:51:30)

Fix form rerender bug
This is the proper fix for #487 and #492. Reverts #495.

codecov[bot]: # [Codecov](https://codecov.io/gh/final-form/react-final-form/pull/498?src=pr&el=h1) Report
> Merging [#498](https://codecov.io/gh/final-form/react-final-form/pull/498?src=pr&el=desc) into [master](https://codecov.io/gh/final-form/react-final-form/commit/34b289b972971cdc4e02fa4073e3f0aeaf94058c?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/498/graphs/tree.svg?width=650&token=s8VUe6IxMZ&height=150&src=pr)](https://codecov.io/gh/final-form/react-final-form/pull/498?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #498   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          14     14           
  Lines         229    231    +2     
  Branches       57     57           
=====================================
+ Hits          229    231    +2
```


| [Impacted Files](https://codecov.io/gh/final-form/react-final-form/pull/498?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ReactFinalForm.js](https://codecov.io/gh/final-form/react-final-form/pull/498/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/498?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/498?src=pr&el=footer). Last update [34b289b...adc5a1d](https://codecov.io/gh/final-form/react-final-form/pull/498?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/ReactFinalForm.js b/src/ReactFinalForm.js
index f74d5c7..e71a627 100644
--- a/src/ReactFinalForm.js
+++ b/src/ReactFinalForm.js
@@ -78,12 +78,17 @@ const ReactFinalForm = ({
     }
   )
 
+  // save a copy of state that can break through the closure
+  // on the shallowEqual() line below.
+  const stateRef = React.useRef<FormState>(state)
+  stateRef.current = state
+
   React.useEffect(() => {
     // We have rendered, so all fields are no registered, so we can unpause validation
     form.isValidationPaused() && form.resumeValidation()
     const unsubscriptions: Unsubscribe[] = [
       form.subscribe(s => {
-        if (!shallowEqual(s, state)) {
+        if (!shallowEqual(s, stateRef.current)) {
           setState(s)
         }
       }, subscription),


Test patch:
diff --git a/src/ReactFinalForm.test.js b/src/ReactFinalForm.test.js
index c725707..94aa74f 100644
--- a/src/ReactFinalForm.test.js
+++ b/src/ReactFinalForm.test.js
@@ -1,5 +1,5 @@
 import React from 'react'
-import { render, fireEvent, cleanup, act } from 'react-testing-library'
+import { render, fireEvent, cleanup } from 'react-testing-library'
 import 'jest-dom/extend-expect'
 import deepEqual from 'fast-deep-equal'
 import { ErrorBoundary, Toggle, wrapWith } from './testUtils'
@@ -857,4 +857,54 @@ describe('ReactFinalForm', () => {
     expect(onSubmitMock).toHaveBeenCalledTimes(1)
     expect(onSubmitMock.mock.calls[0][0]).toEqual({ name: 'erikras' })
   })
+
+  it('should set submitting back to false after submit', async () => {
+    /**
+     * This test causes a warning:
+     *
+     * Warning: An update to ReactFinalForm inside a test was not wrapped in act(...).
+     *
+     * ...but it's working as expected:
+     * 1) We click "Submit"
+     * 2) Submission is async and takes 1ms
+     * 3) The form's state has to go to `submitting = true` and then back to `submitting = false`
+     *
+     * That state change when the submission completes is not an "act" from the
+     * user, but an internal state change. If you have an idea of how to fix this,
+     * please submit a PR. Thanks. -@erikras
+     */
+    const onSubmit = jest.fn(async () => {
+      sleep(1)
+    })
+    const recordSubmitting = jest.fn()
+    const { getByText } = render(
+      <Form onSubmit={onSubmit} subscription={{ submitting: true }}>
+        {({ handleSubmit, submitting }) => {
+          recordSubmitting(submitting)
+          return (
+            <form onSubmit={handleSubmit}>
+              <Field name="name" component="input" />
+              <button type="submit">Submit</button>
+            </form>
+          )
+        }}
+      </Form>
+    )
+    expect(onSubmit).not.toHaveBeenCalled()
+    expect(recordSubmitting).toHaveBeenCalled()
+    expect(recordSubmitting).toHaveBeenCalledTimes(1)
+    expect(recordSubmitting.mock.calls[0][0]).toBe(false)
+
+    fireEvent.click(getByText('Submit'))
+
+    expect(onSubmit).toHaveBeenCalled()
+    expect(onSubmit).toHaveBeenCalledTimes(1)
+    expect(recordSubmitting).toHaveBeenCalledTimes(2)
+    expect(recordSubmitting.mock.calls[1][0]).toBe(true)
+
+    await sleep(5)
+
+    expect(recordSubmitting).toHaveBeenCalledTimes(3)
+    expect(recordSubmitting.mock.calls[2][0]).toBe(false)
+  })
 })