Repository: jaredpalmer/formik (ts)

Issue:
`isValid` is true even when there are invalid fields
#97 introduces `isInitialValid` which marks the form invalid when it's initialized. This gives a confusing result as soon as the form is dirty.

~Because `isValid` works by counting the number of items in `errors`, it doesn't check for fields which haven't had blur events.~ In our project we observed this as a submit button going from disabled to enabled after entering a valid value into an input, even though the other fields weren't valid. This feels like an unexpected behaviour.

Do you have a pattern to deal with this? I understand that you don't want fields to show errors until they're been blurred, but for this case I'd expect formik to keep track of invalid fields even if they haven't been touched

Thanks
Adam

Pull request description:
Fix isValid logic and add tests
Fixes #102 . Adds tests to all logic branches as well to ensure correct future behavior

Patch:
diff --git a/src/formik.tsx b/src/formik.tsx
index bcb5e90..aa7c648 100644
--- a/src/formik.tsx
+++ b/src/formik.tsx
@@ -532,7 +532,8 @@ Formik cannot determine which value to update. For more info see https://github.
             dirty={dirty}
             isValid={
               dirty
-                ? this.state.errors && Object.keys(this.state.errors).length > 0
+                ? this.state.errors &&
+                  Object.keys(this.state.errors).length === 0
                 : isInitialValid !== false && isFunction(isInitialValid)
                   ? (isInitialValid as (props: Props) => boolean)(this.props)
                   : isInitialValid as boolean


Test patch:
diff --git a/test/formik.test.tsx b/test/formik.test.tsx
index f4f0ed9..b9de92e 100644
--- a/test/formik.test.tsx
+++ b/test/formik.test.tsx
@@ -82,34 +82,6 @@ describe('Formik', () => {
     expect(tree.find(Form).props().isValid).toBe(false);
   });
 
-  it('should compute isValid if isInitialValid is present and returns true', () => {
-    const InvalidForm = FormFactory({ isInitialValid: props => true });
-    const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
-    expect(tree.find(Form).props().dirty).toBe(false);
-    expect(tree.find(Form).props().isValid).toBe(true);
-  });
-
-  it('should compute isValid if isInitialValid is present and returns false', () => {
-    const InvalidForm = FormFactory({ isInitialValid: props => false });
-    const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
-    expect(tree.find(Form).props().dirty).toBe(false);
-    expect(tree.find(Form).props().isValid).toBe(false);
-  });
-
-  it('should compute isValid if isInitialValid boolean is present and set to true', () => {
-    const InvalidForm = FormFactory({ isInitialValid: true });
-    const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
-    expect(tree.find(Form).props().dirty).toBe(false);
-    expect(tree.find(Form).props().isValid).toBe(true);
-  });
-
-  it('should compute isValid if isInitialValid is present and set to false', () => {
-    const InvalidForm = FormFactory({ isInitialValid: false });
-    const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
-    expect(tree.find(Form).props().dirty).toBe(false);
-    expect(tree.find(Form).props().isValid).toBe(false);
-  });
-
   describe('FormikHandlers', () => {
     describe('handleChange', () => {
       it('sets values state', async () => {
@@ -538,14 +510,56 @@ describe('Formik', () => {
   });
 
   describe('FormikComputedProps', () => {
-    it('dirty, should update as soon as any input is touched', () => {
-      const tree = mount(<BasicForm user={{ name: 'jared' }} />);
+    it('should compute dirty as soon as any input is touched', () => {
+      const ValidForm = FormFactory();
+      const tree = shallow(<ValidForm user={{ name: 'jared' }} />);
+      expect(tree.find(Form).props().dirty).toBe(false);
+      tree.setState({ touched: { name: true } });
+      expect(tree.find(Form).props().dirty).toBe(true);
+    });
 
+    it('should compute isValid if isInitialValid is present and returns true', () => {
+      const InvalidForm = FormFactory({ isInitialValid: props => true });
+      const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
       expect(tree.find(Form).props().dirty).toBe(false);
+      expect(tree.find(Form).props().isValid).toBe(true);
+    });
 
-      tree.setState({ touched: { name: true } });
+    it('should compute isValid if isInitialValid is present and returns false', () => {
+      const InvalidForm = FormFactory({ isInitialValid: props => false });
+      const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
+      expect(tree.find(Form).props().dirty).toBe(false);
+      expect(tree.find(Form).props().isValid).toBe(false);
+    });
+
+    it('should compute isValid if isInitialValid boolean is present and set to true', () => {
+      const InvalidForm = FormFactory({ isInitialValid: true });
+      const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
+      expect(tree.find(Form).props().dirty).toBe(false);
+      expect(tree.find(Form).props().isValid).toBe(true);
+    });
 
-      expect(tree.update().find(Form).props().dirty).toBe(true);
+    it('should compute isValid if isInitialValid is present and set to false', () => {
+      const InvalidForm = FormFactory({ isInitialValid: false });
+      const tree = shallow(<InvalidForm user={{ name: 'jared' }} />);
+      expect(tree.find(Form).props().dirty).toBe(false);
+      expect(tree.find(Form).props().isValid).toBe(false);
+    });
+
+    it('should compute isValid if the form is dirty and there are errors', () => {
+      const ValidForm = FormFactory();
+      const tree = shallow(<ValidForm user={{ name: 'jared' }} />);
+      tree.setState({ touched: { name: true }, errors: { name: 'Required!' } });
+      expect(tree.find(Form).props().dirty).toBe(true);
+      expect(tree.find(Form).props().isValid).toBe(false);
+    });
+
+    it('should compute isValid if the form is dirty and there are not errors', () => {
+      const ValidForm = FormFactory();
+      const tree = shallow(<ValidForm user={{ name: 'jared' }} />);
+      tree.setState({ touched: { name: true } });
+      expect(tree.find(Form).props().dirty).toBe(true);
+      expect(tree.find(Form).props().isValid).toBe(true);
     });
   });
 });