Repository: final-form/react-final-form
Pull request #697 (2019-12-12 13:28:45)

Submit edge case fix
see #695

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

```diff
@@          Coverage Diff          @@
##           master   #697   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          17     17           
  Lines         251    251           
  Branches       58     58           
=====================================
  Hits          251    251
```


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

------

[Continue to review full report at Codecov](https://codecov.io/gh/final-form/react-final-form/pull/697?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/697?src=pr&el=footer). Last update [add4fcf...c2c18c4](https://codecov.io/gh/final-form/react-final-form/pull/697?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/philosophy.md b/docs/philosophy.md
index b4aa5a8..5b9317f 100644
--- a/docs/philosophy.md
+++ b/docs/philosophy.md
@@ -2,7 +2,7 @@
 
 # Philosophy
 
-For several years, I ([@erikras](https://twitter.com/erikras)) actively maintained the first big form library in the React community, [Redux Form](https://redux-form.com). During those years, I learned many lessons, about open source and React, and saw hundreds of forms use cases from around the world. As Redux Form grew in popularity (and bundle size), I received a lot of feedback from the community. React Final Form is my answer the concerns of the community.
+For several years, I ([@erikras](https://twitter.com/erikras)) actively maintained the first big form library in the React community, [Redux Form](https://redux-form.com). During those years, I learned many lessons, about open source and React, and saw hundreds of forms use cases from around the world. As Redux Form grew in popularity (and bundle size), I received a lot of feedback from the community. React Final Form is my answer to the concerns of the community.
 
 ## Talk
 
diff --git a/examples/field-warnings/index.js b/examples/field-warnings/index.js
index 6e37fb9..57eaf70 100644
--- a/examples/field-warnings/index.js
+++ b/examples/field-warnings/index.js
@@ -1,53 +1,34 @@
-import React from 'react'
-import { render } from 'react-dom'
-import Styles from './Styles'
-import { Form, FormSpy, Field } from 'react-final-form'
-import setFieldData from 'final-form-set-field-data'
+import React from "react";
+import { render } from "react-dom";
+import Styles from "./Styles";
+import { WarningEngine } from "./warning-engine";
+import { Form, Field } from "react-final-form";
+import setFieldData from "final-form-set-field-data";
 
-const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
+const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
 
 const onSubmit = async values => {
-  await sleep(300)
-  window.alert(JSON.stringify(values, 0, 2))
-}
-
-const WarningEngine = ({ mutators: { setFieldData } }) => (
-  <FormSpy
-    subscription={{ values: true }}
-    onChange={({ values }) => {
-      setFieldData('firstName', {
-        warning: values.firstName ? undefined : 'Recommended'
-      })
-      setFieldData('lastName', {
-        warning: values.lastName ? undefined : 'Recommended'
-      })
-    }}
-  />
-)
+  await sleep(300);
+  window.alert(JSON.stringify(values, 0, 2));
+};
 
 const App = () => (
   <Styles>
-    <h1>
-      <span role="img" aria-label="final form flag">
+    <h1><span role="img" aria-label="final form flag">
         🏁
-      </span>{' '}
-      React Final Form Example
+      </span>{' '}React Final Form Example
     </h1>
     <h2>
-      <span role="img" aria-label="warning icon">
-        ⚠️
-      </span>{' '}
-      Warnings{' '}
-      <span role="img" aria-label="warning icon">
-        ⚠️
-      </span>
+      <span role="img" aria-label="final form flag">⚠️</span>
+        {' '}Warnings{' '}
+      <span role="img" aria-label="final form flag">⚠️</span>
     </h2>
     <a href="https://github.com/erikras/react-final-form#-react-final-form">
       Read Docs
     </a>
     <p>
       Warnings, in this example, are defined as: suggestions to the user, like
-      validation errors, but that do not prevent submission. Note that the{' '}
+      validation errors, but that do not prevent submission. Note that the{" "}
       <code>&lt;WarningEngine/&gt;</code> component must be at the bottom of the
       form to guarantee that all the fields have registered.
     </p>
@@ -57,52 +38,54 @@ const App = () => (
       render={({
         handleSubmit,
         reset,
-        mutators,
+        form: { mutators },
         submitting,
         pristine,
         values
-      }) => (
-        <form onSubmit={handleSubmit}>
-          <Field name="firstName">
-            {({ input, meta }) => (
-              <div>
-                <label>First Name</label>
-                <input {...input} placeholder="First Name" />
-                {meta.touched && meta.data.warning && (
-                  <span>{meta.data.warning}</span>
-                )}
-              </div>
-            )}
-          </Field>
-          <Field name="lastName">
-            {({ input, meta }) => (
-              <div>
-                <label>Last Name</label>
-                <input {...input} placeholder="Last Name" />
-                {meta.touched && meta.data.warning && (
-                  <span>{meta.data.warning}</span>
-                )}
-              </div>
-            )}
-          </Field>
-          <div className="buttons">
-            <button type="submit" disabled={submitting}>
-              Submit
-            </button>
-            <button
-              type="button"
-              onClick={reset}
-              disabled={submitting || pristine}
-            >
-              Reset
-            </button>
-          </div>
-          <pre>{JSON.stringify(values, 0, 2)}</pre>
-          <WarningEngine mutators={mutators} />
-        </form>
-      )}
+      }) => {
+        return (
+          <form onSubmit={handleSubmit}>
+            <Field name="firstName">
+              {({ input, meta }) => (
+                <div>
+                  <label>First Name</label>
+                  <input {...input} placeholder="First Name" />
+                  {meta.touched && meta.data.warning && (
+                    <span>{meta.data.warning}</span>
+                  )}
+                </div>
+              )}
+            </Field>
+            <Field name="lastName">
+              {({ input, meta }) => (
+                <div>
+                  <label>Last Name</label>
+                  <input {...input} placeholder="Last Name" />
+                  {meta.touched && meta.data.warning && (
+                    <span>{meta.data.warning}</span>
+                  )}
+                </div>
+              )}
+            </Field>
+            <div className="buttons">
+              <button type="submit" disabled={submitting}>
+                Submit
+              </button>
+              <button
+                type="button"
+                onClick={reset}
+                disabled={submitting || pristine}
+              >
+                Reset
+              </button>
+            </div>
+            <pre>{JSON.stringify(values, 0, 2)}</pre>
+            <WarningEngine mutators={mutators} />
+          </form>
+        );
+      }}
     />
   </Styles>
-)
+);
 
-render(<App />, document.getElementById('root'))
+render(<App />, document.getElementById("root"));
diff --git a/examples/field-warnings/readme.md b/examples/field-warnings/readme.md
index 3c77638..93d53a9 100644
--- a/examples/field-warnings/readme.md
+++ b/examples/field-warnings/readme.md
@@ -1,3 +1,3 @@
 # Field Warnings
 
-[![Edit react-final-form-field-warnings-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/m5qwxpr6o8)
+[![Edit react-final-form-field-warnings-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/react-final-form-warnings-0nimt)
diff --git a/examples/field-warnings/warning-engine.js b/examples/field-warnings/warning-engine.js
new file mode 100644
index 0000000..0aada15
--- /dev/null
+++ b/examples/field-warnings/warning-engine.js
@@ -0,0 +1,16 @@
+import React from "react";
+import { FormSpy } from "react-final-form";
+
+export const WarningEngine = ({ mutators: { setFieldData } }) => (
+  <FormSpy
+    subscription={{ values: true }}
+    onChange={({ values }) => {
+      setFieldData("firstName", {
+        warning: values.firstName ? undefined : "Recommended"
+      });
+      setFieldData("lastName", {
+        warning: values.lastName ? undefined : "Recommended"
+      });
+    }}
+  />
+);
diff --git a/src/useField.js b/src/useField.js
index 0892bee..cf73c0f 100644
--- a/src/useField.js
+++ b/src/useField.js
@@ -50,6 +50,11 @@ function useField<FormValues: FormValuesShape>(
   const configRef = useLatest(config)
 
   const register = (callback: FieldState => void) =>
+    // avoid using `state` const in any closures created inside `register`
+    // because they would refer `state` from current execution context
+    // whereas actual `state` would defined in the subsequent `useField` hook
+    // execution
+    // (that would be caused by `setState` call performed in `register` callback)
     form.registerField(name, callback, subscription, {
       afterSubmit,
       beforeSubmit: () => {
@@ -60,11 +65,11 @@ function useField<FormValues: FormValuesShape>(
         } = configRef.current
 
         if (formatOnBlur) {
-          const { value } = ((form.getFieldState(state.name): any): FieldState)
-          const formatted = format(value, state.name)
+          const { value } = ((form.getFieldState(name): any): FieldState)
+          const formatted = format(value, name)
 
           if (formatted !== value) {
-            state.change(formatted)
+            form.change(name, formatted)
           }
         }
 


Test patch:
diff --git a/src/Field.test.js b/src/Field.test.js
index b8070ee..888eaf5 100644
--- a/src/Field.test.js
+++ b/src/Field.test.js
@@ -1164,4 +1164,39 @@ describe('Field', () => {
     fireEvent.click(getByText('Toggle'))
     expect(validate).toHaveBeenCalledTimes(1)
   })
+
+  it('submit should not throw when field with enabled `formatOnBlur` changes name `prop`', () => {
+    const onSubmit = jest.fn()
+
+    const trim = value => value && value.trim()
+
+    const { getByTestId, getByText } = render(
+      <Form onSubmit={onSubmit}>
+        {({ handleSubmit }) => (
+          <Toggle>
+            {newFieldName => (
+              <form onSubmit={handleSubmit}>
+                <Field
+                  name={newFieldName ? 'newName' : 'oldName'}
+                  component="input"
+                  formatOnBlur={true}
+                  format={trim}
+                  data-testid="field"
+                />
+                <button type="submit">Submit</button>
+              </form>
+            )}
+          </Toggle>
+        )}
+      </Form>
+    )
+
+    fireEvent.click(getByText('Toggle'))
+    fireEvent.change(getByTestId('field'), {
+      target: { value: 'trailing space ' }
+    })
+    fireEvent.click(getByText('Submit'))
+    expect(onSubmit).toHaveBeenCalled()
+    expect(onSubmit.mock.calls[0][0]).toEqual({ newName: 'trailing space' })
+  })
 })