Repository: jaredpalmer/formik
Pull request #513 (2018-03-14 08:17:33)

Stop bundling external dependencies for cjs and esm bundles
Currently `formik` bundles contain all external dependencies, which add unnecessary KBs to final app bundles.

Before: ~190KB
After: ~43KB


Patch:
diff --git a/README.md b/README.md
index 9cb6d18..74a361a 100644
--- a/README.md
+++ b/README.md
@@ -303,7 +303,7 @@ can install Yup from npm...
 npm install yup --save
 ```
 
-** Table of Contents **
+## Table of Contents
 
 <!-- START doctoc generated TOC please keep comment here to allow auto update -->
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@@ -326,19 +326,20 @@ npm install yup --save
       - [`handleBlur: (e: any) => void`](#handleblur-e-any--void)
       - [`handleChange: (e: React.ChangeEvent<any>) => void`](#handlechange-e-reactchangeeventany--void)
       - [`handleReset: () => void`](#handlereset---void)
-      - [`handleSubmit: (e: React.FormEvent<HTMLFormEvent> | undefined) => void`](#handlesubmit-e-reactformeventhtmlformevent--void)
+      - [`handleSubmit: (e: React.FormEvent<HTMLFormEvent>) => void`](#handlesubmit-e-reactformeventhtmlformevent--void)
       - [`isSubmitting: boolean`](#issubmitting-boolean)
       - [`isValid: boolean`](#isvalid-boolean)
       - [`resetForm: (nextValues?: Values) => void`](#resetform-nextvalues-values--void)
       - [`setErrors: (fields: { [field: string]: string }) => void`](#seterrors-fields--field-string-string---void)
       - [`setFieldError: (field: string, errorMsg: string) => void`](#setfielderror-field-string-errormsg-string--void)
       - [`setFieldTouched: (field: string, isTouched: boolean, shouldValidate?: boolean) => void`](#setfieldtouched-field-string-istouched-boolean-shouldvalidate-boolean--void)
+      - [`submitForm: () => void`](#submitform---void)
+      - [`submitCount: number`](#submitcount-number)
       - [`setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void`](#setfieldvalue-field-string-value-any-shouldvalidate-boolean--void)
       - [`setStatus: (status?: any) => void`](#setstatus-status-any--void)
       - [`setSubmitting: (isSubmitting: boolean) => void`](#setsubmitting-issubmitting-boolean--void)
       - [`setTouched: (fields: { [field: string]: boolean }) => void`](#settouched-fields--field-string-boolean---void)
       - [`setValues: (fields: { [field: string]: any }) => void`](#setvalues-fields--field-string-any---void)
-      - [`submitForm`](#submitform-fields--field-string-any---void)
       - [`status?: any`](#status-any)
       - [`touched: { [field: string]: boolean }`](#touched--field-string-boolean-)
       - [`values: { [field: string]: any }`](#values--field-string-any-)
@@ -972,6 +973,11 @@ Set the touched state of a field imperatively. `field` should match the key of
 
 Trigger a form submission.
 
+##### `submitCount: number`
+
+Number of times user tried to submit the form. Increases when [`handleSubmit`](#handlesubmit-values-values-formikbag-formikbag--void) is called, resets after calling  
+[`handleReset`](#handlereset---void). `submitCount` is readonly computed property and should not be mutated directly.
+
 ##### `setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void`
 
 Set the value of a field imperatively. `field` should match the key of
@@ -1312,7 +1318,7 @@ slightly relaxed and allow you to return a `Function` (e.g. `i18n('invalid')`).
 
 ```jsx
 import React from 'react';
-import { Formik, Form, Field, FieldArray } from 'formik'
+import { Formik, Form, Field, FieldArray } from 'formik';
 
 // Here is an example of a form with an editable list.
 // Next to each input are buttons for insert and remove.
@@ -1327,11 +1333,11 @@ export const FriendList = () => (
           alert(JSON.stringify(values, null, 2));
         }, 500)
       }
-      render={formikProps => (
+      render={({ values }) => (
         <FieldArray
           name="friends"
           render={arrayHelpers => (
-          <Form>
+            <Form>
               {values.friends && values.friends.length > 0 ? (
                 values.friends.map((friend, index) => (
                   <div>
@@ -1351,10 +1357,7 @@ export const FriendList = () => (
                   </div>
                 ))
               ) : (
-                <button
-                  type="button"
-                  onClick={() => arrayHelpers.push('')}
-                >
+                <button type="button" onClick={() => arrayHelpers.push('')}>
                   {/* show this when user has removed all friends from the list */}
                   Add a friend
                 </button>
diff --git a/rollup.config.js b/rollup.config.js
index 2c64a94..10e68a4 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -4,6 +4,7 @@ import replace from 'rollup-plugin-replace';
 import resolve from 'rollup-plugin-node-resolve';
 import sourceMaps from 'rollup-plugin-sourcemaps';
 import uglify from 'rollup-plugin-uglify';
+import pkg from './package.json';
 
 const shared = {
   input: `compiled/index.js`,
@@ -66,44 +67,21 @@ export default [
   }),
 
   Object.assign({}, shared, {
+    external: shared.external.concat(Object.keys(pkg.dependencies)),
     output: [
       {
         file: 'dist/formik.es6.js',
         format: 'es',
         sourcemap: true,
-        exports: 'named',
-        globals: {
-          react: 'React',
-          'react-native': 'ReactNative',
-        },
       },
       {
         file: 'dist/formik.js',
         format: 'cjs',
         sourcemap: true,
-        exports: 'named',
-        globals: {
-          react: 'React',
-          'react-native': 'ReactNative',
-        },
       },
     ],
     plugins: [
       resolve(),
-      commonjs({
-        include: /node_modules/,
-        namedExports: {
-          'node_modules/prop-types/index.js': [
-            'object',
-            'oneOfType',
-            'string',
-            'node',
-            'func',
-            'bool',
-            'element',
-          ],
-        },
-      }),
       sourceMaps(),
     ],
   }),
diff --git a/src/Formik.tsx b/src/Formik.tsx
index b9800de..15c10df 100644
--- a/src/Formik.tsx
+++ b/src/Formik.tsx
@@ -55,6 +55,8 @@ export interface FormikState<Values> {
   isSubmitting: boolean;
   /** Top level status state, in case you need it */
   status?: any;
+  /** Number of times user tried to submit the form */
+  submitCount: number;
 }
 
 /**
@@ -288,6 +290,7 @@ export class Formik<ExtraProps = {}, Values = object> extends React.Component<
       errors: {},
       touched: {},
       isSubmitting: false,
+      submitCount: 0,
     };
     this.fields = {};
     this.initialValues = props.initialValues || ({} as any);
@@ -499,13 +502,14 @@ export class Formik<ExtraProps = {}, Values = object> extends React.Component<
 
   submitForm = () => {
     // Recursively set all values to `true`.
-    this.setState({
+    this.setState(prevState => ({
       touched: setNestedObjectValues<FormikTouched<Values>>(
-        this.state.values,
+        prevState.values,
         true
       ),
       isSubmitting: true,
-    });
+      submitCount: prevState.submitCount + 1,
+    }));
 
     if (this.props.validate) {
       const maybePromisedErrors =
@@ -615,6 +619,7 @@ export class Formik<ExtraProps = {}, Values = object> extends React.Component<
       error: undefined,
       status: undefined,
       values,
+      submitCount: 0,
     });
 
     Object.keys(this.fields).map(f => this.fields[f]());


Test patch:
diff --git a/test/formik.test.tsx b/test/Formik.test.tsx
similarity index 96%
rename from test/formik.test.tsx
rename to test/Formik.test.tsx
index 3f55d3d..a09ea69 100644
--- a/test/formik.test.tsx
+++ b/test/Formik.test.tsx
@@ -68,6 +68,7 @@ describe('<Formik>', () => {
     expect(tree.find(Form).props().errors).toEqual({});
     expect(tree.find(Form).props().dirty).toBe(false);
     expect(tree.find(Form).props().isValid).toBe(false);
+    expect(tree.find(Form).props().submitCount).toBe(0);
   });
 
   describe('FormikHandlers', () => {
@@ -852,6 +853,21 @@ describe('<Formik>', () => {
       expect(tree.find(Form).props().dirty).toBe(true);
       expect(tree.find(Form).props().isValid).toBe(true);
     });
+
+    it('should increase submitCount after submitting the form', async () => {
+      const tree = shallow(BasicForm);
+      expect(tree.find(Form).props().submitCount).toBe(0);
+      await tree
+        .find(Form)
+        .props()
+        .submitForm();
+      expect(
+        tree
+          .update()
+          .find(Form)
+          .props().submitCount
+      ).toBe(1);
+    });
   });
 
   describe('componentWillReceiveProps', () => {
@@ -1044,5 +1060,33 @@ describe('<Formik>', () => {
           .props().dirty
       ).toEqual(false);
     });
+
+    it('should reset submitCount', () => {
+      const tree = mount(<WithState />);
+
+      tree
+        .find(Form)
+        .props()
+        .handleSubmit();
+
+      expect(
+        tree
+          .update()
+          .find(Form)
+          .props().submitCount
+      ).toEqual(1);
+
+      tree
+        .find(Form)
+        .props()
+        .handleReset();
+
+      expect(
+        tree
+          .update()
+          .find(Form)
+          .props().submitCount
+      ).toEqual(0);
+    });
   });
 });