Repository: erikras/redux-form
Pull request #445 (2015-12-18 10:39:36)

Save field name as asyncValidating prop
Based on your comments I've changed the code. Please have a look. To show loading icon you could write:
```
asyncValidating === field.name ? <i className="fa fa-spinner fa-spin fa-2x"></i> : null;
```


Patch:
diff --git a/src/actions.js b/src/actions.js
index 12eb1bac..10af1798 100644
--- a/src/actions.js
+++ b/src/actions.js
@@ -29,8 +29,8 @@ export const removeArrayValue = (path, index) =>
 export const reset = () =>
   ({type: RESET});
 
-export const startAsyncValidation = () =>
-  ({type: START_ASYNC_VALIDATION});
+export const startAsyncValidation = field =>
+  ({type: START_ASYNC_VALIDATION, field});
 
 export const startSubmit = () =>
   ({type: START_SUBMIT});
diff --git a/src/asyncValidation.js b/src/asyncValidation.js
index 750de8cc..5c4bb47d 100644
--- a/src/asyncValidation.js
+++ b/src/asyncValidation.js
@@ -1,8 +1,8 @@
 import isPromise from 'is-promise';
 import isValid from './isValid';
 
-const asyncValidation = (fn, start, stop) => {
-  start();
+const asyncValidation = (fn, start, stop, field) => {
+  start(field);
   const promise = fn();
   if (!isPromise(promise)) {
     throw new Error('asyncValidate function passed to reduxForm must return a promise');
diff --git a/src/createHigherOrderComponent.js b/src/createHigherOrderComponent.js
index d89f8fd8..d41192ac 100644
--- a/src/createHigherOrderComponent.js
+++ b/src/createHigherOrderComponent.js
@@ -68,7 +68,7 @@ const createHigherOrderComponent = (config,
           // if blur validating, only run async validate if sync validation passes
           if (!name || isValid(syncErrors[name])) {
             return asyncValidation(() =>
-              asyncValidate(values, dispatch, this.props), startAsyncValidation, stopAsyncValidation);
+              asyncValidate(values, dispatch, this.props), startAsyncValidation, stopAsyncValidation, name);
           }
         }
       }
diff --git a/src/getValuesFromState.js b/src/getValuesFromState.js
index 4baff56f..70f1f85b 100644
--- a/src/getValuesFromState.js
+++ b/src/getValuesFromState.js
@@ -18,12 +18,8 @@ const getValuesFromState = state => {
         }
       } else if (Array.isArray(field)) {
         accumulator[key] = field.map(arrayField => arrayField.value || getValuesFromState(arrayField));
-      } else if (typeof field === 'object') {
-        const result = getValuesFromState(field);
-
-        if (result && Object.keys(result).length > 0) {
-          accumulator[key] = result;
-        }
+      } else if (typeof field !== 'string') {
+        accumulator[key] = getValuesFromState(field);
       }
     }
     return accumulator;
diff --git a/src/reducer.js b/src/reducer.js
index 41e95ba0..d476f080 100644
--- a/src/reducer.js
+++ b/src/reducer.js
@@ -23,7 +23,7 @@ const behaviors = {
     const array = read(path, state);
     const stateCopy = {...state};
     const arrayCopy = array ? [...array] : [];
-    const newValue = value !== null && typeof value === 'object' ? initializeState(value, Object.keys(value)) : {value};
+    const newValue = {value};
     if (index === undefined) {
       arrayCopy.push(newValue);
     } else {
@@ -95,10 +95,10 @@ const behaviors = {
       _submitFailed: false
     };
   },
-  [START_ASYNC_VALIDATION](state) {
+  [START_ASYNC_VALIDATION](state, {field}) {
     return {
       ...state,
-      _asyncValidating: true
+      _asyncValidating: field || true
     };
   },
   [START_SUBMIT](state) {


Test patch:
diff --git a/src/__tests__/actions.spec.js b/src/__tests__/actions.spec.js
index c0488854..c770008c 100644
--- a/src/__tests__/actions.spec.js
+++ b/src/__tests__/actions.spec.js
@@ -93,7 +93,10 @@ describe('actions', () => {
   });
 
   it('should create startAsyncValidation action', () => {
-    expect(startAsyncValidation()).toEqual({type: START_ASYNC_VALIDATION});
+    expect(startAsyncValidation('myField')).toEqual({
+      type: START_ASYNC_VALIDATION,
+      field: 'myField'
+    });
   });
 
   it('should create startSubmit action', () => {
diff --git a/src/__tests__/asyncValidation.spec.js b/src/__tests__/asyncValidation.spec.js
index a47b452c..e6139180 100644
--- a/src/__tests__/asyncValidation.spec.js
+++ b/src/__tests__/asyncValidation.spec.js
@@ -3,12 +3,13 @@ import isPromise from 'is-promise';
 import asyncValidation from '../asyncValidation';
 
 describe('asyncValidation', () => {
+  const field = 'myField';
 
   it('should throw an error if fn does not return a promise', () => {
     const fn = () => null;
     const start = () => null;
     const stop = () => null;
-    expect(() => asyncValidation(fn, start, stop))
+    expect(() => asyncValidation(fn, start, stop, field))
       .toThrow(/promise/);
   });
 
@@ -16,16 +17,18 @@ describe('asyncValidation', () => {
     const fn = () => Promise.resolve();
     const start = () => null;
     const stop = () => null;
-    expect(isPromise(asyncValidation(fn, start, stop))).toBe(true);
+    expect(isPromise(asyncValidation(fn, start, stop, field))).toBe(true);
   });
 
   it('should call start, fn, and stop on promise resolve', () => {
     const fn = createSpy().andReturn(Promise.resolve());
     const start = createSpy();
     const stop = createSpy();
-    const promise = asyncValidation(fn, start, stop);
+    const promise = asyncValidation(fn, start, stop, field);
     expect(fn).toHaveBeenCalled();
-    expect(start).toHaveBeenCalled();
+    expect(start)
+      .toHaveBeenCalled()
+      .toHaveBeenCalledWith(field);
     return promise.then(() => {
       expect(stop).toHaveBeenCalled();
     }, () => {
@@ -37,9 +40,11 @@ describe('asyncValidation', () => {
     const fn = createSpy().andReturn(Promise.reject());
     const start = createSpy();
     const stop = createSpy();
-    const promise = asyncValidation(fn, start, stop);
+    const promise = asyncValidation(fn, start, stop, field);
     expect(fn).toHaveBeenCalled();
-    expect(start).toHaveBeenCalled();
+    expect(start)
+      .toHaveBeenCalled()
+      .toHaveBeenCalledWith(field);
     return promise.then(() => {
       expect(false).toBe(true); // should not get into resolve branch
     }, () => {
@@ -52,9 +57,11 @@ describe('asyncValidation', () => {
     const fn = createSpy().andReturn(Promise.reject(errors));
     const start = createSpy();
     const stop = createSpy();
-    const promise = asyncValidation(fn, start, stop);
+    const promise = asyncValidation(fn, start, stop, field);
     expect(fn).toHaveBeenCalled();
-    expect(start).toHaveBeenCalled();
+    expect(start)
+      .toHaveBeenCalled()
+      .toHaveBeenCalledWith(field);
     return promise.then(() => {
       expect(false).toBe(true); // should not get into resolve branch
     }, () => {
diff --git a/src/__tests__/getValues.spec.js b/src/__tests__/getValues.spec.js
index 689b5430..24aec93a 100644
--- a/src/__tests__/getValues.spec.js
+++ b/src/__tests__/getValues.spec.js
@@ -110,22 +110,4 @@ describe('getValues', () => {
         bar: [{deeper: 42}]
       });
   });
-
-  it('should ignore visited fields without values', () => {
-    const form = {
-      foo: {
-        value: 'dog'
-      },
-      bar: {
-        visited: true
-      }
-    };
-    const fields = ['foo', 'bar'];
-    expect(getValues(fields, form))
-      .toBeA('object')
-      .toEqual({
-        foo: 'dog',
-        bar: undefined
-      });
-  });
 });
diff --git a/src/__tests__/getValuesFromState.spec.js b/src/__tests__/getValuesFromState.spec.js
index 89a93339..5c7c21a5 100644
--- a/src/__tests__/getValuesFromState.spec.js
+++ b/src/__tests__/getValuesFromState.spec.js
@@ -177,20 +177,4 @@ describe('getValuesFromState', () => {
         bar: 'cat'
       });
   });
-
-  it('should ignore visited fields without values', () => {
-    const state = {
-      foo: {
-        value: 'dog'
-      },
-      bar: {
-        visited: true
-      }
-    };
-    expect(getValuesFromState(state))
-      .toBeA('object')
-      .toEqual({
-        foo: 'dog'
-      });
-  });
 });
diff --git a/src/__tests__/reducer.spec.js b/src/__tests__/reducer.spec.js
index 4f3ace49..fb546fd1 100644
--- a/src/__tests__/reducer.spec.js
+++ b/src/__tests__/reducer.spec.js
@@ -78,7 +78,7 @@ describe('reducer', () => {
       });
   });
 
-  it('should add a deep array value with initial value', () => {
+  it('should add a deep array value with empty state', () => {
     const state = reducer({}, {
       ...addArrayValue('myField.myArray', 20, undefined),
       form: 'foo'
@@ -184,329 +184,6 @@ describe('reducer', () => {
       });
   });
 
-  it('should push an array value which is a deep object', () => {
-    const state = reducer({
-      testForm: {
-        friends: [
-          {
-            name: {
-              initial: 'name-1',
-              value: 'name-1'
-            },
-            address: {
-              street: {
-                initial: 'street-1',
-                value: 'street-1'
-              },
-              postalCode: {
-                initial: 'postalCode-1',
-                value: 'postalCode-1'
-              }
-            }
-          },
-          {
-            name: {
-              initial: 'name-2',
-              value: 'name-2'
-            },
-            address: {
-              street: {
-                initial: 'street-2',
-                value: 'street-2'
-              },
-              postalCode: {
-                initial: 'postalCode-2',
-                value: 'postalCode-2'
-              }
-            }
-          }
-        ],
-        _active: undefined,
-        _asyncValidating: false,
-        _error: undefined,
-        _submitting: false,
-        _submitFailed: false
-      }
-    }, {
-      ...addArrayValue('friends', {
-        name: 'name-3',
-        address: {
-          street: 'street-3',
-          postalCode: 'postalCode-3'
-        }
-      }, undefined),
-      form: 'testForm'
-    });
-    expect(state.testForm)
-      .toEqual({
-        friends: [
-          {
-            name: {
-              initial: 'name-1',
-              value: 'name-1'
-            },
-            address: {
-              street: {
-                initial: 'street-1',
-                value: 'street-1'
-              },
-              postalCode: {
-                initial: 'postalCode-1',
-                value: 'postalCode-1'
-              }
-            }
-          },
-          {
-            name: {
-              initial: 'name-2',
-              value: 'name-2'
-            },
-            address: {
-              street: {
-                initial: 'street-2',
-                value: 'street-2'
-              },
-              postalCode: {
-                initial: 'postalCode-2',
-                value: 'postalCode-2'
-              }
-            }
-          },
-          {
-            name: {
-              initial: 'name-3',
-              value: 'name-3'
-            },
-            address: {
-              street: {
-                initial: 'street-3',
-                value: 'street-3'
-              },
-              postalCode: {
-                initial: 'postalCode-3',
-                value: 'postalCode-3'
-              }
-            }
-          }
-        ],
-        _active: undefined,
-        _asyncValidating: false,
-        _error: undefined,
-        _submitting: false,
-        _submitFailed: false
-      });
-  });
-
-  it('should push a deep array value which is a nested object', () => {
-    const state = reducer({
-      testForm: {
-        myField: [
-          {
-            foo: {
-              initial: {a: 'foo-a1', b: 'foo-b1'},
-              value: {a: 'foo-a1', b: 'foo-b1'},
-            },
-            bar: {
-              initial: {a: 'bar-a1', b: 'bar-b1'},
-              value: {a: 'bar-a1', b: 'bar-b1'},
-            }
-          },
-          {
-            foo: {
-              initial: {a: 'foo-a2', b: 'foo-b2'},
-              value: {a: 'foo-a2', b: 'foo-b2'},
-            },
-            bar: {
-              initial: {a: 'bar-a2', b: 'bar-b2'},
-              value: {a: 'bar-a2', b: 'bar-b2'},
-            }
-          },
-        ],
-        _active: undefined,
-        _asyncValidating: false,
-        _error: undefined,
-        _submitting: false,
-        _submitFailed: false
-      }
-    }, {
-      ...addArrayValue('myField', {foo: {a: 'foo-a3', b: 'foo-b3'}, bar: {a: 'bar-a3', b: 'bar-b3'}}, undefined),
-      form: 'testForm'
-    });
-    expect(state.testForm)
-      .toEqual({
-        myField: [
-          {
-            foo: {
-              initial: {a: 'foo-a1', b: 'foo-b1'},
-              value: {a: 'foo-a1', b: 'foo-b1'},
-            },
-            bar: {
-              initial: {a: 'bar-a1', b: 'bar-b1'},
-              value: {a: 'bar-a1', b: 'bar-b1'},
-            }
-          },
-          {
-            foo: {
-              initial: {a: 'foo-a2', b: 'foo-b2'},
-              value: {a: 'foo-a2', b: 'foo-b2'},
-            },
-            bar: {
-              initial: {a: 'bar-a2', b: 'bar-b2'},
-              value: {a: 'bar-a2', b: 'bar-b2'},
-            }
-          },
-          {
-            foo: {
-              initial: {a: 'foo-a3', b: 'foo-b3'},
-              value: {a: 'foo-a3', b: 'foo-b3'},
-            },
-            bar: {
-              initial: {a: 'bar-a3', b: 'bar-b3'},
-              value: {a: 'bar-a3', b: 'bar-b3'},
-            }
-          },
-        ],
-        _active: undefined,
-        _asyncValidating: false,
-        _error: undefined,
-        _submitting: false,
-        _submitFailed: false
-      });
-  });
-
-  it('should push a subarray value which is an object', () => {
-    const state = reducer({
-      testForm: {
-        myField: [
-          {
-            myField2: [
-              {
-                foo: {
-                  initial: 'foo-1-1',
-                  value: 'foo-1-1'
-                },
-                bar: {
-                  initial: 'bar-1-1',
-                  value: 'bar-1-1'
-                }
-              },
-              {
-                foo: {
-                  initial: 'foo-1-2',
-                  value: 'foo-1-2'
-                },
-                bar: {
-                  initial: 'bar-1-2',
-                  value: 'bar-1-2'
-                }
-              },
-            ],
-          },
-          {
-            myField2: [
-              {
-                foo: {
-                  initial: 'foo-2-1',
-                  value: 'foo-2-1'
-                },
-                bar: {
-                  initial: 'bar-2-1',
-                  value: 'bar-2-1'
-                }
-              },
-              {
-                foo: {
-                  initial: 'foo-2-2',
-                  value: 'foo-2-2'
-                },
-                bar: {
-                  initial: 'bar-2-2',
-                  value: 'bar-2-2'
-                }
-              },
-            ],
-          },
-        ],
-        _active: undefined,
-        _asyncValidating: false,
-        _error: undefined,
-        _submitting: false,
-        _submitFailed: false
-      }
-    }, {
-      ...addArrayValue('myField[1].myField2', {foo: 'foo-2-3', bar: 'bar-2-3'}, undefined),
-      form: 'testForm'
-    });
-    expect(state.testForm)
-      .toEqual({
-        myField: [
-          {
-            myField2: [
-              {
-                foo: {
-                  initial: 'foo-1-1',
-                  value: 'foo-1-1'
-                },
-                bar: {
-                  initial: 'bar-1-1',
-                  value: 'bar-1-1'
-                }
-              },
-              {
-                foo: {
-                  initial: 'foo-1-2',
-                  value: 'foo-1-2'
-                },
-                bar: {
-                  initial: 'bar-1-2',
-                  value: 'bar-1-2'
-                }
-              },
-            ],
-          },
-          {
-            myField2: [
-              {
-                foo: {
-                  initial: 'foo-2-1',
-                  value: 'foo-2-1'
-                },
-                bar: {
-                  initial: 'bar-2-1',
-                  value: 'bar-2-1'
-                }
-              },
-              {
-                foo: {
-                  initial: 'foo-2-2',
-                  value: 'foo-2-2'
-                },
-                bar: {
-                  initial: 'bar-2-2',
-                  value: 'bar-2-2'
-                }
-              },
-              {
-                foo: {
-                  initial: 'foo-2-3',
-                  value: 'foo-2-3'
-                },
-                bar: {
-                  initial: 'bar-2-3',
-                  value: 'bar-2-3'
-                }
-              },
-            ],
-          },
-        ],
-        _active: undefined,
-        _asyncValidating: false,
-        _error: undefined,
-        _submitting: false,
-        _submitFailed: false
-      });
-  });
-
   it('should set value on blur with empty state', () => {
     const state = reducer({}, {
       ...blur('myField', 'myValue'),
@@ -1320,6 +997,41 @@ describe('reducer', () => {
       });
   });
 
+  it('should set asyncValidating with field name on startAsyncValidation', () => {
+    const state = reducer({
+      foo: {
+        myField: {
+          initial: 'initialValue',
+          value: 'initialValue'
+        },
+        doesnt: 'matter',
+        should: 'notchange',
+        _active: undefined,
+        _asyncValidating: false,
+        [globalErrorKey]: undefined,
+        _submitting: false,
+        _submitFailed: false
+      }
+    }, {
+      ...startAsyncValidation('myField'),
+      form: 'foo'
+    });
+    expect(state.foo)
+      .toEqual({
+        myField: {
+          initial: 'initialValue',
+          value: 'initialValue'
+        },
+        doesnt: 'matter',
+        should: 'notchange',
+        _active: undefined,
+        _asyncValidating: 'myField',
+        [globalErrorKey]: undefined,
+        _submitting: false,
+        _submitFailed: false
+      });
+  });
+
   it('should set submitting on startSubmit', () => {
     const state = reducer({
       foo: {