diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000..4acdec3f8
Binary files /dev/null and b/.DS_Store differ
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index 623b1f417..98aedd41e 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -1,4 +1,5 @@
{
"sandboxes": ["ooh2y"],
- "buildCommand": "build"
+ "buildCommand": "build",
+ "packages": ["packages/formik", "packages/formik-native"]
}
diff --git a/.gitignore b/.gitignore
index 24e89f786..55b2a17d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-node_modules
dist
compiled
*.log
@@ -8,19 +7,14 @@ next.d.ts
legacy.d.ts
.idea
*.orig
-.DS_Store
node_modules
-
-lib/core/metadata.js
-lib/core/MetadataBlog.js
+package-lock.json
+yarn.lock
+!/yarn.lock
website/translated_docs
website/build/
website/yarn.lock
website/node_modules
website/i18n/*
-.rts2_cache_es
-.rts2_cache_esm
-.rts2_cache_cjs
-.rts2_cache_umd
\ No newline at end of file
diff --git a/.storybook/config.js b/.storybook/config.js
deleted file mode 100644
index 075f86897..000000000
--- a/.storybook/config.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { configure } from '@storybook/react';
-import { setOptions } from '@storybook/addon-options';
-
-// Option defaults:
-setOptions({
- /**
- * Name to display in the top left corner
- * @type {String}
- */
- name: 'Formik',
- /**
- * URL for name in top left corner to link to
- * @type {String}
- */
- url: 'https://github.com/jaredpalmer/formik',
- /**
- * Show story component as full screen
- * @type {Boolean}
- */
- goFullScreen: false,
- /**
- * Display left panel that shows a list of stories
- * @type {Boolean}
- */
- showLeftPanel: true,
- /**
- * Display horizontal panel that displays addon configurations
- * @type {Boolean}
- */
- showDownPanel: false,
- /**
- * Display floating search box to search through stories
- * @type {Boolean}
- */
- showSearchBox: false,
- /**
- * Show horizontal addons panel as a vertical panel on the right
- * @type {Boolean}
- */
- downPanelInRight: false,
- /**
- * Sorts stories
- * @type {Boolean}
- */
- sortStoriesByKind: false,
- /**
- * Regex for finding the hierarchy separator
- * @example:
- * null - turn off hierarchy
- * /\// - split by `/`
- * /\./ - split by `.`
- * /\/|\./ - split by `/` or `.`
- * @type {Regex}
- */
- hierarchySeparator: null,
-
- /**
- * Sidebar tree animations
- * @type {Boolean}
- */
- sidebarAnimations: true,
-
- /**
- * ID to select an addon panel
- * @type {String}
- */
- selectedAddonPanel: undefined, // The order of addons in the "Addons Panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook
-});
-
-function loadStories() {
- require('./example.stories');
-}
-
-configure(loadStories, module);
diff --git a/.storybook/example.stories.js b/.storybook/example.stories.js
deleted file mode 100644
index ba3d7e038..000000000
--- a/.storybook/example.stories.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import './story.css';
-import { action, storiesOf, module } from '@storybook/react';
-import React from 'react';
-import { FormikConsumer } from 'formik';
-import AsyncValidation from '../examples/AsyncValidation';
-import Arrays from '../examples/Arrays';
-import Basic from '../examples/Basic.js';
-import CustomInputs from '../examples/CustomInputs';
-import ErrorMessage from '../examples/ErrorMessage';
-import FastField from '../examples/FastField';
-import MultistepWizard from '../examples/MultistepWizard';
-import SchemaValidation from '../examples/SchemaValidation';
-import SyncValidation from '../examples/SyncValidation';
-import FieldLevelValidation from '../examples/FieldLevelValidation';
-import CombinedValidations from '../examples/CombinedValidations';
-import DebouncedAutoSave from '../examples/DebouncedAutoSave';
-
-const AsyncValidationCode = require('!raw-loader!../examples/AsyncValidation')
- .default;
-const ArraysCode = require('!raw-loader!../examples/Arrays').default;
-const BasicCode = require('!raw-loader!../examples/Basic.js').default;
-const DebouncedAutoSaveCode = require('!raw-loader!../examples/DebouncedAutoSave.js')
- .default;
-const CustomInputsCode = require('!raw-loader!../examples/CustomInputs')
- .default;
-const ErrorMessageCode = require('!raw-loader!../examples/ErrorMessage')
- .default;
-const FastFieldCode = require('!raw-loader!../examples/FastField').default;
-const MultistepWizardCode = require('!raw-loader!../examples/MultistepWizard')
- .default;
-const SchemaValidationCode = require('!raw-loader!../examples/SchemaValidation')
- .default;
-const SyncValidationCode = require('!raw-loader!../examples/SyncValidation')
- .default;
-const FieldLevelValidationCode = require('!raw-loader!../examples/FieldLevelValidation')
- .default;
-const CombinedValidationsCode = require('!raw-loader!../examples/CombinedValidations')
- .default;
-
-function cleanExample(str) {
- return str
- .replace(`import { Debug } from './Debug';`, '')
- .replace(`
{cleanExample(BasicCode)}
- {cleanExample(ArraysCode)}
- {cleanExample(AsyncValidationCode)}
- {cleanExample(CustomInputsCode)}
- {cleanExample(DebouncedAutoSaveCode)}
- {cleanExample(ErrorMessageCode)}
- {cleanExample(FastFieldCode)}
- {cleanExample(MultistepWizardCode)}
- {cleanExample(SchemaValidationCode)}
- {cleanExample(SyncValidationCode)}
- {cleanExample(FieldLevelValidationCode)}
- {cleanExample(CombinedValidationsCode)}
- - This example demonstrates how to properly create checkboxes with Formik. -
-
-
-
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 | + + + + + + + + + + + + + + + + + + + + + + + +5x + + + + + +5x + + + +24x +24x + +24x +24x + + + +24x + + + +24x + + + + + +24x +24x + + + + +24x + + + + + + +24x + +24x + +24x + + + + +24x + + + + +24x + + + + + +5x +19x + + +24x +2x +2x +2x +2x +2x +2x +2x +2x + +2x +2x + + +2x + +1x +1x +1x + + + + + + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1x + + + +24x +2x +2x + + +2x +1x +1x + + + + + + + + + + + + + + + + + +1x + + + + + + + +1x + + + + + + + + +5x +26x +26x +26x +26x +26x +52x +26x +26x + + +26x +26x +26x + +26x + + + + + + + + +26x + + + + + +26x +5x + + + + + + +21x +4x + + +17x +10x +10x + + + + + + + +7x + + + + + +5x + +5x + + + | import * as React from 'react'; +import isEqual from 'react-fast-compare'; +import warning from 'warning'; +import { polyfill } from 'react-lifecycles-compat'; +import { FieldAttributes, FieldConfig, FieldProps } from './Field'; +import { validateYupSchema, yupToFormErrors } from './Formik'; +import { connect } from './connect'; +import { FormikContext } from './types'; +import { getIn, isEmptyChildren, isFunction, isPromise, setIn } from './utils'; + +export interface FastFieldState { + value: any; + error?: string; +} + +/** @private Returns whether two objects are deeply equal **excluding** a key / dot path */ +function isEqualExceptForKey(a: any, b: any, path: string) { + return isEqual(setIn(a, path, undefined), setIn(b, path, undefined)); +} + +/** + * Custom Field component for quickly hooking into Formik + * context and wiring up forms. + */ +class FastFieldInner<Props = {}, Values = {}> extends React.Component< + FieldAttributes<Props> & { formik: FormikContext<Values> }, + FastFieldState +> { + reset: (nextValues?: any) => void; + + static getDerivedStateFromProps( + nextProps: any /* FieldAttributes<Props> & { formik: FormikContext<Values> }*/, + prevState: FastFieldState + ) { + const nextFieldValue = getIn(nextProps.formik.values, nextProps.name); + const nextFieldError = getIn(nextProps.formik.errors, nextProps.name); + + let nextState = null; + Iif (!isEqual(nextFieldValue, prevState.value)) { + nextState = { ...prevState, value: nextFieldValue }; + } + + Iif (!isEqual(nextFieldError, prevState.error)) { + nextState = { ...prevState, error: nextFieldError }; + } + + return nextState; + } + + constructor( + props: FieldAttributes<Props> & { formik: FormikContext<Values> } + ) { + super(props); + this.state = { + value: getIn(props.formik.values, props.name), + error: getIn(props.formik.errors, props.name), + }; + + this.reset = (nextValues?: any) => { + this.setState({ + value: getIn(nextValues, props.name), + error: getIn(props.formik.errors, props.name), + }); + }; + + props.formik.registerField(props.name, this.reset); + + const { render, children, component } = props; + + warning( + !(component && render), + 'You should not use <FastField component> and <FastField render> in the same <FastField> component; <FastField component> will be ignored' + ); + + warning( + !(props.component && children && isFunction(children)), + 'You should not use <FastField component> and <FastField children> as a function in the same <FastField> component; <FastField component> will be ignored.' + ); + + warning( + !(render && children && !isEmptyChildren(children)), + 'You should not use <FastField render> and <FastField children> in the same <FastField> component; <FastField children> will be ignored' + ); + } + + componentWillUnmount() { + this.props.formik.unregisterField(this.props.name); + } + + handleChange = (e: React.ChangeEvent<any>) => { + e.persist(); + const { + validateOnChange, + validate, + values, + validationSchema, + errors, + setFormikState, + } = this.props.formik; + const { type, value, checked } = e.target; + const val = /number|range/.test(type) + ? parseFloat(value) + : /checkbox/.test(type) ? checked : value; + if (validateOnChange) { + // Field-level validation + Eif (this.props.validate) { + const maybePromise = (this.props.validate as any)(value); + Iif (isPromise(maybePromise)) { + this.setState({ value: val }); + (maybePromise as any).then( + () => this.setState({ error: undefined }), + (error: string) => this.setState({ error }) + ); + } else { + this.setState({ value: val, error: maybePromise }); + } + } else if (validate) { + // Top-level validate + const maybePromise = (validate as any)( + setIn(values, this.props.name, val) + ); + + if (isPromise(maybePromise)) { + this.setState({ value: val }); + (maybePromise as any).then( + () => this.setState({ error: undefined }), + (error: any) => { + // Here we diff the errors object relative to Formik parents except for + // the Field's key. If they are equal, the field's validation function is + // has no inter-field side-effects and we only need to update local state + // otherwise we need to lift up the update to the parent (causing a full form render) + if (isEqualExceptForKey(maybePromise, errors, this.props.name)) { + this.setState({ error: getIn(error, this.props.name) }); + } else { + setFormikState((prevState: any) => ({ + ...prevState, + errors: error, + // touched: setIn(prevState.touched, name, true), + })); + } + } + ); + } else { + // Handle the same diff situation + // @todo refactor + if (isEqualExceptForKey(maybePromise, errors, this.props.name)) { + this.setState({ + value: val, + error: getIn(maybePromise, this.props.name), + }); + } else { + this.setState({ + value: val, + }); + setFormikState((prevState: any) => ({ + ...prevState, + errors: maybePromise, + })); + } + } + } else if (validationSchema) { + // Top-level validationsSchema + const schema = isFunction(validationSchema) + ? validationSchema() + : validationSchema; + const mergedValues = setIn(values, this.props.name, val); + // try to validate with yup synchronously if possible...saves a render. + try { + validateYupSchema(mergedValues, schema, true); + this.setState({ + value: val, + error: undefined, + }); + } catch (e) { + if (e.name === 'ValidationError') { + this.setState({ + value: val, + error: getIn(yupToFormErrors(e), this.props.name), + }); + } else { + this.setState({ + value: val, + }); + // try yup async validation + validateYupSchema(mergedValues, schema).then( + () => this.setState({ error: undefined }), + (err: any) => + this.setState(s => ({ + ...s, + error: getIn(yupToFormErrors(err), this.props.name), + })) + ); + } + } + } else { + this.setState({ value: val }); + } + } else { + this.setState({ value: val }); + } + }; + + handleBlur = () => { + const { validateOnBlur, setFormikState } = this.props.formik; + const { name, validate } = this.props; + + // @todo refactor + if (validateOnBlur && validate) { + const maybePromise = (validate as any)(this.state.value); + Iif (isPromise(maybePromise)) { + (maybePromise as Promise<any>).then( + () => + setFormikState((prevState: any) => ({ + ...prevState, + values: setIn(prevState.values, name, this.state.value), + errors: setIn(prevState.errors, name, undefined), + touched: setIn(prevState.touched, name, true), + })), + error => + setFormikState((prevState: any) => ({ + ...prevState, + values: setIn(prevState.values, name, this.state.value), + errors: setIn(prevState.errors, name, error), + touched: setIn(prevState.touched, name, true), + })) + ); + } else { + setFormikState((prevState: any) => ({ + ...prevState, + values: setIn(prevState.values, name, this.state.value), + errors: setIn(prevState.errors, name, maybePromise), + touched: setIn(prevState.touched, name, true), + })); + } + } else { + setFormikState((prevState: any) => ({ + ...prevState, + errors: setIn(prevState.errors, name, this.state.error), + values: setIn(prevState.values, name, this.state.value), + touched: setIn(prevState.touched, name, true), + })); + } + }; + + render() { + const { + validate, + name, + render, + children, + component = 'input', + formik, + ...props + } = this.props as FieldConfig & { formik: FormikContext<Values> }; + const { + validate: _validate, + validationSchema: _validationSchema, + ...restOfFormik + } = formik; + const field = { + value: + props.type === 'radio' || props.type === 'checkbox' + ? props.value // React uses checked={} for these inputs + : this.state.value, + name, + onChange: this.handleChange, + onBlur: this.handleBlur, + }; + const bag = { + field, + form: restOfFormik, + meta: { touched: getIn(formik.touched, name), error: this.state.error }, + }; + + if (render) { + return (render as ( + props: FieldProps<any> & { + meta: { error?: string; touched?: boolean }; + } + ) => React.ReactNode)(bag); + } + + if (isFunction(children)) { + return (children as (props: FieldProps<any>) => React.ReactNode)(bag); + } + + if (typeof component === 'string') { + const { innerRef, ...rest } = props; + return React.createElement(component as any, { + ref: innerRef, + ...field, + ...rest, + children, + }); + } + + return React.createElement(component as any, { + ...bag, + ...props, + children, + }); + } +} + +export const FastField = connect<FieldAttributes<any>, any>( + polyfill(FastFieldInner) +); + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +5x + + + + + + +24x +24x +24x + + + + +24x + + + + +24x + + + + + +24x +2x +2x +2x +1x + + + +24x +2x +2x +2x +1x + + + +24x +2x +2x + +2x + +2x + + + + + + +2x + + + +5x +24x +24x +24x +24x +24x +48x +24x +24x + + + + +24x +24x +24x + +24x + + + + + + + + +24x + +24x +5x + + +19x +4x + + +15x +8x +8x + + + + + + + +7x + + + + + +5x + +5x + | import * as React from 'react'; +import warning from 'warning'; +import { connect } from './connect'; +import { + FormikProps, + GenericFieldHTMLAttributes, + FormikContext, +} from './types'; +import { getIn, isEmptyChildren, isFunction, isPromise } from './utils'; + +/** + * Note: These typings could be more restrictive, but then it would limit the + * reusability of custom <Field/> components. + * + * @example + * interface MyProps { + * ... + * } + * + * export const MyInput: React.SFC<MyProps & FieldProps> = ({ + * field, + * form, + * ...props + * }) => + * <div> + * <input {...field} {...props}/> + * {form.touched[field.name] && form.errors[field.name]} + * </div> + */ +export interface FieldProps<V = any> { + field: { + /** Classic React change handler, keyed by input name */ + onChange: (e: React.ChangeEvent<any>) => void; + /** Mark input as touched */ + onBlur: (e: any) => void; + /** Value of the input */ + value: any; + /* name of the input */ + name: string; + }; + form: FormikProps<V>; // if ppl want to restrict this for a given form, let them. +} + +export interface FieldConfig { + /** + * Field component to render. Can either be a string like 'select' or a component. + */ + component?: + | string + | React.ComponentType<FieldProps<any>> + | React.ComponentType<void>; + + /** + * Render prop (works like React router's <Route render={props =>} />) + */ + render?: ((props: FieldProps<any>) => React.ReactNode); + + /** + * Children render function <Field name>{props => ...}</Field>) + */ + children?: ((props: FieldProps<any>) => React.ReactNode) | React.ReactNode; + + /** + * Validate a single field value independently + */ + validate?: ((value: any) => string | Function | Promise<void> | undefined); + + /** + * Field name + */ + name: string; + + /** HTML input type */ + type?: string; + + /** Field value */ + value?: any; + + /** Inner ref */ + innerRef?: (instance: any) => void; +} + +export type FieldAttributes<T> = GenericFieldHTMLAttributes & FieldConfig & T; + +/** + * Custom Field component for quickly hooking into Formik + * context and wiring up forms. + */ +class FieldInner<Props = {}, Values = {}> extends React.Component< + FieldAttributes<Props> & { formik: FormikContext<Values> }, + {} +> { + constructor( + props: FieldAttributes<Props> & { formik: FormikContext<Values> } + ) { + super(props); + const { render, children, component } = this.props; + warning( + !(component && render), + 'You should not use <Field component> and <Field render> in the same <Field> component; <Field component> will be ignored' + ); + + warning( + !(component && children && isFunction(children)), + 'You should not use <Field component> and <Field children> as a function in the same <Field> component; <Field component> will be ignored.' + ); + + warning( + !(render && children && !isEmptyChildren(children)), + 'You should not use <Field render> and <Field children> in the same <Field> component; <Field children> will be ignored' + ); + } + + handleChange = (e: React.ChangeEvent<any>) => { + const { handleChange, validateOnChange } = this.props.formik; + handleChange(e); // Call Formik's handleChange no matter what + if (!!validateOnChange && !!this.props.validate) { + this.runFieldValidations(e.target.value); + } + }; + + handleBlur = (e: any) => { + const { handleBlur, validateOnBlur } = this.props.formik; + handleBlur(e); // Call Formik's handleBlur no matter what + if (!!validateOnBlur && !!this.props.validate) { + this.runFieldValidations(e.target.value); + } + }; + + runFieldValidations = (value: any) => { + const { setFieldError } = this.props.formik; + const { name, validate } = this.props; + // Call validate fn + const maybePromise = (validate as any)(value); + // Check if validate it returns a Promise + Iif (isPromise(maybePromise)) { + (maybePromise as Promise<any>).then( + () => setFieldError(name, undefined as any), + error => setFieldError(name, error) + ); + } else { + // Otherwise set the error + setFieldError(name, maybePromise); + } + }; + + render() { + const { + validate, + name, + render, + children, + component = 'input', + formik, + ...props + } = (this.props as FieldAttributes<Props> & { + formik: FormikContext<Values>; + }) as any; + const { + validate: _validate, + validationSchema: _validationSchema, + ...restOfFormik + } = formik; + const field = { + value: + props.type === 'radio' || props.type === 'checkbox' + ? props.value // React uses checked={} for these inputs + : getIn(formik.values, name), + name, + onChange: validate ? this.handleChange : formik.handleChange, + onBlur: validate ? this.handleBlur : formik.handleBlur, + }; + const bag = { field, form: restOfFormik }; + + if (render) { + return (render as any)(bag); + } + + if (isFunction(children)) { + return (children as (props: FieldProps<any>) => React.ReactNode)(bag); + } + + if (typeof component === 'string') { + const { innerRef, ...rest } = props; + return React.createElement(component as any, { + ref: innerRef, + ...field, + ...rest, + children, + }); + } + + return React.createElement(component as any, { + ...bag, + ...props, + children, + }); + } +} + +export const Field = connect<FieldAttributes<any>, any>(FieldInner); + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +5x + + + + + + + +5x +1x +1x +1x +1x +1x + + +5x +1x +1x +1x + + +5x +1x +1x +1x + +5x + + + +5x + + + + +10x + +10x +10x + + +10x + + + + +7x +7x +7x +7x + +7x +7x + + + + + + + + + + +7x +7x + + + + + +10x +1x +1x + + + + +10x + +10x +1x +1x + + + + +10x + + +10x + + + + + + +10x + +10x +1x +1x + + + + +10x + +10x +1x +1x + + + + +10x + + +10x +1x +1x + +1x +1x + + + + +1x + + +10x + +5x + + +1x + + +3x +3x +1x + +3x +3x + +3x + + + + + +1x + + +10x + +5x + + +1x + + +3x +3x +1x + +3x + + + + + +1x + + +10x + +5x +17x + + + + + + + + + + + + + + + + + + +17x +17x +17x +17x +17x +17x +17x +17x +17x + + + +17x + +17x + + + + + + + + + +5x + +5x + | import * as React from 'react'; +import { connect } from './connect'; +import { + FormikContext, + FormikState, + SharedRenderProps, + FormikProps, +} from './types'; +import { getIn, isEmptyChildren, isFunction, setIn } from './utils'; + +export type FieldArrayConfig = { + /** Really the path to the array field to be updated */ + name: string; + /** Should field array validate the form AFTER array updates/changes? */ + validateOnChange?: boolean; +} & SharedRenderProps<ArrayHelpers & { form: FormikProps<any> }>; +export interface ArrayHelpers { + /** Imperatively add a value to the end of an array */ + push: (obj: any) => void; + /** Curried fn to add a value to the end of an array */ + handlePush: (obj: any) => () => void; + /** Imperatively swap two values in an array */ + swap: (indexA: number, indexB: number) => void; + /** Curried fn to swap two values in an array */ + handleSwap: (indexA: number, indexB: number) => () => void; + /** Imperatively move an element in an array to another index */ + move: (from: number, to: number) => void; + /** Imperatively move an element in an array to another index */ + handleMove: (from: number, to: number) => () => void; + /** Imperatively insert an element at a given index into the array */ + insert: (index: number, value: any) => void; + /** Curried fn to insert an element at a given index into the array */ + handleInsert: (index: number, value: any) => () => void; + /** Imperatively replace a value at an index of an array */ + replace: (index: number, value: any) => void; + /** Curried fn to replace an element at a given index into the array */ + handleReplace: (index: number, value: any) => () => void; + /** Imperatively add an element to the beginning of an array and return its length */ + unshift: (value: any) => number; + /** Curried fn to add an element to the beginning of an array */ + handleUnshift: (value: any) => () => void; + /** Curried fn to remove an element at an index of an array */ + handleRemove: (index: number) => () => void; + /** Curried fn to remove a value from the end of the array */ + handlePop: () => () => void; + /** Imperatively remove and element at an index of an array */ + remove<T>(index: number): T | undefined; + /** Imperatively remove and return value from the end of the array */ + pop<T>(): T | undefined; +} + +/** + * Some array helpers! + */ +export const move = (array: any[], from: number, to: number) => { + const copy = [...(array || [])]; + const value = copy[from]; + copy.splice(from, 1); + copy.splice(to, 0, value); + return copy; +}; + +export const swap = (array: any[], indexA: number, indexB: number) => { + const copy = [...(array || [])]; + const a = copy[indexA]; + copy[indexA] = copy[indexB]; + copy[indexB] = a; + return copy; +}; + +export const insert = (array: any[], index: number, value: any) => { + const copy = [...(array || [])]; + copy.splice(index, 0, value); + return copy; +}; + +export const replace = (array: any[], index: number, value: any) => { + const copy = [...(array || [])]; + copy[index] = value; + return copy; +}; +class FieldArrayInner<Values = {}> extends React.Component< + FieldArrayConfig & { formik: FormikContext<Values> }, + {} +> { + static defaultProps = { + validateOnChange: true, + }; + + constructor(props: FieldArrayConfig & { formik: FormikContext<Values> }) { + super(props); + // We need TypeScript generics on these, so we'll bind them in the constructor + this.remove = this.remove.bind(this); + this.pop = this.pop.bind(this); + } + + updateArrayField = ( + fn: Function, + alterTouched: boolean, + alterErrors: boolean + ) => { + const { + name, + validateOnChange, + formik: { setFormikState, validateForm, values, touched, errors }, + } = this.props; + setFormikState( + (prevState: FormikState<any>) => ({ + ...prevState, + values: setIn(prevState.values, name, fn(getIn(values, name))), + errors: alterErrors + ? setIn(prevState.errors, name, fn(getIn(errors, name))) + : prevState.errors, + touched: alterTouched + ? setIn(prevState.touched, name, fn(getIn(touched, name))) + : prevState.touched, + }), + () => { + Eif (validateOnChange) { + validateForm(); + } + } + ); + }; + + push = (value: any) => + this.updateArrayField( + (array: any[]) => [...(array || []), value], + false, + false + ); + + handlePush = (value: any) => () => this.push(value); + + swap = (indexA: number, indexB: number) => + this.updateArrayField( + (array: any[]) => swap(array, indexA, indexB), + false, + false + ); + + handleSwap = (indexA: number, indexB: number) => () => + this.swap(indexA, indexB); + + move = (from: number, to: number) => + this.updateArrayField( + (array: any[]) => move(array, from, to), + false, + false + ); + + handleMove = (from: number, to: number) => () => this.move(from, to); + + insert = (index: number, value: any) => + this.updateArrayField( + (array: any[]) => insert(array, index, value), + false, + false + ); + + handleInsert = (index: number, value: any) => () => this.insert(index, value); + + replace = (index: number, value: any) => + this.updateArrayField( + (array: any[]) => replace(array, index, value), + false, + false + ); + + handleReplace = (index: number, value: any) => () => + this.replace(index, value); + + unshift = (value: any) => { + let arr = []; + this.updateArrayField( + (array: any[]) => { + arr = array ? [value, ...array] : [value]; + return arr; + }, + false, + false + ); + return arr.length; + }; + + handleUnshift = (value: any) => () => this.unshift(value); + + remove<T>(index: number): T { + // We need to make sure we also remove relevant pieces of `touched` and `errors` + let result: any; + this.updateArrayField( + // so this gets call 3 times + (array?: any[]) => { + const copy = array ? [...array] : []; + if (!result) { + result = copy[index]; + } + Eif (isFunction(copy.splice)) { + copy.splice(index, 1); + } + return copy; + }, + true, + true + ); + + return result; + } + + handleRemove = (index: number) => () => this.remove<any>(index); + + pop<T>(): T { + // Remove relevant pieces of `touched` and `errors` too! + let result: any; + this.updateArrayField( + // so this gets call 3 times + (array: any[]) => { + const tmp = array; + if (!result) { + result = tmp && tmp.pop && tmp.pop(); + } + return tmp; + }, + true, + true + ); + + return result; + } + + handlePop = () => () => this.pop<any>(); + + render() { + const arrayHelpers: ArrayHelpers = { + push: this.push, + pop: this.pop, + swap: this.swap, + move: this.move, + insert: this.insert, + replace: this.replace, + unshift: this.unshift, + remove: this.remove, + handlePush: this.handlePush, + handlePop: this.handlePop, + handleSwap: this.handleSwap, + handleMove: this.handleMove, + handleInsert: this.handleInsert, + handleReplace: this.handleReplace, + handleUnshift: this.handleUnshift, + handleRemove: this.handleRemove, + }; + + const { + component, + render, + children, + name, + formik: { + validate: _validate, + validationSchema: _validationSchema, + ...restOfFormik + }, + } = this.props; + + const props = { ...arrayHelpers, form: restOfFormik, name }; + + return component + ? React.createElement(component as any, props) + : render + ? (render as any)(props) + : children // children come last, always called + ? typeof children === 'function' + ? (children as any)(props) + : !isEmptyChildren(children) ? React.Children.only(children) : null + : null; + } +} + +export const FieldArray = connect<FieldArrayConfig, any>(FieldArrayInner); + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+1 +2 +3 +4 +5 +6 +7 +8 +9 | + + +5x + + + +5x + | import * as React from 'react'; +import { connect } from './connect'; + +export const Form = connect<any>(({ formik: { handleSubmit }, ...props }) => ( + <form onSubmit={handleSubmit} {...props} /> +)); + +Form.displayName = 'Form'; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 | + + + +5x + + +5x + + +5x +5x + + + +5x + | import { isReactNative } from './utils'; + +// React polyfill for some next-level unstable optimizations +let ReactRenderer; +Iif (!!isReactNative) { + ReactRenderer = require('react-native'); +} else { + ReactRenderer = require('react-dom'); +} + +let { unstable_batchedUpdates } = ReactRenderer; +Iif (unstable_batchedUpdates === undefined) { + unstable_batchedUpdates = (fn: any) => fn(); +} + +export const batchUpdate = unstable_batchedUpdates; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 | + + + + + +5x +5x + + + + + + + + + +56x + +56x + + + + +20x + + + +20x + + + + + + + + + + + + | import * as React from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import createContext from 'create-react-context'; +import { FormikContext } from './types'; + +export const { + Provider: FormikProvider, + Consumer: FormikConsumer, +} = createContext<FormikContext<any>>({} as any); + +/** + * Connect any component to Formik context, and inject as a prop called `formik`; + * @param Comp React Component + */ +export function connect<OuterProps, Values = {}>( + Comp: React.ComponentType<OuterProps & { formik: FormikContext<Values> }> +) { + const C: React.SFC<OuterProps> = (props: OuterProps) => ( + <FormikConsumer> + {formik => <Comp {...props} formik={formik} />} + </FormikConsumer> + ); + // Assign Comp to C.WrappedComponent so we can access the inner component in tests + // For example, <Field.WrappedComponent /> gets us <FieldInner/> + (C as React.SFC<OuterProps> & { + WrappedComponent: React.ReactNode; + }).WrappedComponent = Comp; + + return hoistNonReactStatics< + OuterProps, + OuterProps & { formik: FormikContext<Values> } + >( + C, + Comp as React.ComponentClass<OuterProps & { formik: FormikContext<Values> }> // cast type to ComponentClass (even if SFC) + ) as React.ComponentClass<OuterProps> & { + WrappedComponent: React.ComponentClass< + OuterProps & { formik: FormikContext<Values> } + >; + }; +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 | + + + + + + + + + + + + + + + + + + + + + + + +5x + + + +5x + + + + + + + + +129x + + +129x + + + + + +129x +129x + + + + + + +129x +129x + +129x + + + + +129x + + + + +129x + + + + + +129x +19x + + +129x +19x + + +5x + +44x + + + +2x + +2x + + + +129x +11x + + +129x +3x +3x +3x + + + + +129x +3x +3x + +3x +2x + + + + +129x +1x + + +129x + + + + + + + + +129x + + + + + + +129x +4x +4x + + +4x + +4x +4x +2x + + + + + + + + + + +129x +33x +2x + + +33x +10x +10x + + + + + + + +10x + + + + +129x + + + + + + +11x + + + + + + +11x +11x + + + +11x + + +11x +11x + +11x +11x +11x +11x +11x +11x +11x + +11x +11x + + + + + + +11x + + + + +11x + +11x + + + + +11x +8x + + + + + + +11x + + + + + + + + + + + +11x + + + +129x + + +8x + + +7x + + + +4x +4x + +4x +3x + + + + +129x +24x +21x + + + + + + +24x + + + + +24x +24x + + + +2x + + + + + + + + + +24x + + +129x + +29x + + + + + + + + +29x + +14x +14x +8x + +6x +6x + + + +8x + +6x +6x + + + + + +6x +4x + + +15x +2x + +13x + + + +129x +25x + + +129x +7x +7x +7x + +7x +7x + +7x + + + + + + + +7x + + + +7x +7x + + + +7x + + + + + + +7x + + + +129x + +4x +8x + + +4x +4x + + + + +4x +3x + + + + + +129x + +1x + + + + + +129x +4x + +4x + +4x + + + + + + + + +4x + + +129x +5x +2x + + + + +2x +1x + +1x + + +3x + + + +129x +7x + +129x +477x + + + + + + + + + + + + + + + + +129x +450x +450x +450x + + + + + + + + + + +129x +450x + + + + + + + + + + + + + + + + +129x +225x + + + + + + +5x +225x +225x +225x +225x + + + + + + + + + + + + + + + +5x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +8x +8x + +4x +4x +4x +4x +4x + + +4x + + + + + | import * as React from 'react'; +import isEqual from 'react-fast-compare'; +import warning from 'warning'; +import { FormikProvider } from './connect'; +import { + FormikActions, + FormikConfig, + FormikErrors, + FormikState, + FormikTouched, + FormikValues, + FormikContext, +} from './types'; +import { + isEmptyChildren, + isFunction, + isNaN, + isPromise, + isString, + setIn, + setNestedObjectValues, + getActiveElement, +} from './utils'; +import { batchUpdate } from './batchUpdate'; +export class Formik<ExtraProps = {}, Values = object> extends React.Component< + FormikConfig<Values> & ExtraProps, + FormikState<any> +> { + static defaultProps = { + validateOnChange: true, + validateOnBlur: true, + isInitialValid: false, + enableReinitialize: false, + }; + + initialValues: Values; + + hcCache: { + [key: string]: (e: string | React.ChangeEvent<any>) => void; + } = {}; + hbCache: { + [key: string]: (e: any) => void; + } = {}; + fields: { [field: string]: (nextValues?: any) => void }; + + constructor(props: FormikConfig<Values> & ExtraProps) { + super(props); + this.state = { + values: props.initialValues || ({} as any), + errors: {}, + touched: {}, + isSubmitting: false, + submitCount: 0, + }; + this.fields = {}; + this.initialValues = props.initialValues || ({} as any); + + warning( + !(props.component && props.render), + 'You should not use <Formik component> and <Formik render> in the same <Formik> component; <Formik render> will be ignored' + ); + + warning( + !(props.component && props.children && !isEmptyChildren(props.children)), + 'You should not use <Formik component> and <Formik children> in the same <Formik> component; <Formik children> will be ignored' + ); + + warning( + !(props.render && props.children && !isEmptyChildren(props.children)), + 'You should not use <Formik render> and <Formik children> in the same <Formik> component; <Formik children> will be ignored' + ); + } + + registerField = (name: string, resetFn: (nextValues?: any) => void) => { + this.fields[name] = resetFn; + }; + + unregisterField = (name: string) => { + delete this.fields[name]; + }; + + componentDidUpdate(prevProps: Readonly<FormikConfig<Values> & ExtraProps>) { + // If the initialValues change, reset the form + if ( + this.props.enableReinitialize && + !isEqual(prevProps.initialValues, this.props.initialValues) + ) { + this.initialValues = this.props.initialValues; + // @todo refactor to use getDerivedStateFromProps? + this.resetForm(this.props.initialValues); + } + } + + setErrors = (errors: FormikErrors<Values>) => { + this.setState({ errors }); + }; + + setTouched = (touched: FormikTouched<Values>) => { + this.setState({ touched }, () => { + Eif (this.props.validateOnBlur) { + this.runValidations(this.state.values); + } + }); + }; + + setValues = (values: FormikValues) => { + batchUpdate(() => { + this.setState({ values }); + + if (this.props.validateOnChange) { + this.runValidations(values); + } + }); + }; + + setStatus = (status?: any) => { + this.setState({ status }); + }; + + setError = (error: any) => { + if (process.env.NODE_ENV !== 'production') { + console.warn( + `Warning: Formik\'s setError(error) is deprecated and may be removed in future releases. Please use Formik\'s setStatus(status) instead. It works identically. For more info see https://github.com/jaredpalmer/formik#setstatus-status-any--void` + ); + } + this.setState({ error }); + }; + + setSubmitting = (isSubmitting: boolean) => { + this.setState({ isSubmitting }); + }; + + /** + * Run validation against a Yup schema and optionally run a function if successful + */ + runValidationSchema = (values: FormikValues, onSuccess?: Function) => { + const { validationSchema } = this.props; + const schema = isFunction(validationSchema) + ? validationSchema() + : validationSchema; + validateYupSchema(values, schema).then( + () => { + this.setState({ errors: {} }); + if (onSuccess) { + onSuccess(); + } + }, + (err: any) => + this.setState({ errors: yupToFormErrors(err), isSubmitting: false }) + ); + }; + + /** + * Run validations and update state accordingly + */ + runValidations = (values: FormikValues = this.state.values) => { + if (this.props.validationSchema) { + this.runValidationSchema(values); + } + + if (this.props.validate) { + const maybePromisedErrors = (this.props.validate as any)(values); + Iif (isPromise(maybePromisedErrors)) { + (maybePromisedErrors as Promise<any>).then( + () => { + this.setState({ errors: {} }); + }, + errors => this.setState({ errors, isSubmitting: false }) + ); + } else { + this.setErrors(maybePromisedErrors as FormikErrors<Values>); + } + } + }; + + handleChange = ( + eventOrPath: string | React.ChangeEvent<any> + ): void | ((eventOrTextValue: string | React.ChangeEvent<any>) => void) => { + // @todo someone make this less disgusting. + // + // executeChange is the core of handleChange, we'll use it cache change + // handlers like Preact's linkState. + const executeChange = ( + eventOrTextValue: string | React.ChangeEvent<any>, + maybePath?: string + ) => { + // By default, assume that the first argument is a string. This allows us to use + // handleChange with React Native and React Native Web's onChangeText prop which + // provides just the value of the input. + let field = maybePath; + let val = eventOrTextValue; + let parsed; + // If the first argument is not a string though, it has to be a synthetic React Event (or a fake one), + // so we handle like we would a normal HTML change event. + Eif (!isString(eventOrTextValue)) { + // If we can, persist the event + // @see https://reactjs.org/docs/events.html#event-pooling + Eif ((eventOrTextValue as React.ChangeEvent<any>).persist) { + (eventOrTextValue as React.ChangeEvent<any>).persist(); + } + const { + type, + name, + id, + value, + checked, + outerHTML, + } = (eventOrTextValue as React.ChangeEvent<any>).target; + field = maybePath ? maybePath : name ? name : id; + Iif (!field && process.env.NODE_ENV !== 'production') { + warnAboutMissingIdentifier({ + htmlContent: outerHTML, + documentationAnchorLink: 'handlechange-e-reactchangeeventany--void', + handlerName: 'handleChange', + }); + } + val = /number|range/.test(type) + ? ((parsed = parseFloat(value)), isNaN(parsed) ? '' : parsed) + : /checkbox/.test(type) ? checked : value; + } + + Eif (field) { + // Set form fields by name + this.setState(prevState => ({ + ...prevState, + values: setIn(prevState.values, field!, val), + })); + + if (this.props.validateOnChange) { + this.runValidations(setIn(this.state.values, field, val)); + } + } + }; + + // Actually execute logic above.... + // cache these handlers by key like Preact's linkState does for perf boost + Iif (isString(eventOrPath)) { + return isFunction(this.hcCache[eventOrPath]) + ? this.hcCache[eventOrPath] // return the cached handled + : (this.hcCache[eventOrPath] = ( + // make a new one + event: React.ChangeEvent<any> | string + ) => + executeChange( + event /* string or event, does not matter */, + eventOrPath /* this is path to the field now */ + )); + } else { + executeChange(eventOrPath); + } + }; + + setFieldValue = ( + field: string, + value: any, + EshouldValidate: boolean = true + ) => { + // Set form field by name + const valuesState = (prevState: any) => ({ + values: setIn(prevState.values, field, value), + }); + + batchUpdate(() => { + this.setState(valuesState); + + if (this.props.validateOnChange && shouldValidate) { + this.runValidations(valuesState(this.state).values); + } + }); + }; + + handleSubmit = (e: React.FormEvent<HTMLFormElement> | undefined) => { + if (e && e.preventDefault) { + e.preventDefault(); + } + + // Warn if form submission is triggered by a <button> without a + // specified `type` attribute during development. This mitigates + // a common gotcha in forms with both reset and submit buttons, + // where the dev forgets to add type="button" to the reset button. + Eif ( + process.env.NODE_ENV !== 'production' && + typeof document !== 'undefined' + ) { + // Safely get the active element (works with IE) + const activeElement = getActiveElement(); + if ( + activeElement !== null && + activeElement instanceof HTMLButtonElement + ) { + warning( + !!( + activeElement.attributes && + activeElement.attributes.getNamedItem('type') + ), + 'You submitted a Formik form using a button with an unspecified `type` attribute. Most browsers default button elements to `type="submit"`. If this is not a submit button, please add `type="button"`.' + ); + } + } + + this.submitForm(); + }; + + submitForm = () => { + // Recursively set all values to `true`. + this.setState(prevState => ({ + touched: setNestedObjectValues<FormikTouched<Values>>( + prevState.values, + true + ), + isSubmitting: true, + submitCount: prevState.submitCount + 1, + })); + + if (this.props.validate) { + const maybePromisedErrors = + (this.props.validate as any)(this.state.values) || {}; + if (isPromise(maybePromisedErrors)) { + (maybePromisedErrors as Promise<any>).then( + () => { + this.setState({ errors: {} }); + this.executeSubmit(); + }, + errors => this.setState({ errors, isSubmitting: false }) + ); + return; + } else { + const isValid = Object.keys(maybePromisedErrors).length === 0; + this.setState({ + errors: maybePromisedErrors as FormikErrors<Values>, + isSubmitting: isValid, + }); + + // only submit if there are no errors + if (isValid) { + this.executeSubmit(); + } + } + } else if (this.props.validationSchema) { + this.runValidationSchema(this.state.values, this.executeSubmit); + } else { + this.executeSubmit(); + } + }; + + executeSubmit = () => { + this.props.onSubmit(this.state.values, this.getFormikActions()); + }; + + handleBlur = (eventOrString: any): void | ((e: any) => void) => { + const executeBlur = (e: any, path?: string) => { + Eif (e.persist) { + e.persist(); + } + const { name, id, outerHTML } = e.target; + const field = path ? path : name ? name : id; + + Iif (!field && process.env.NODE_ENV !== 'production') { + warnAboutMissingIdentifier({ + htmlContent: outerHTML, + documentationAnchorLink: 'handleblur-e-any--void', + handlerName: 'handleBlur', + }); + } + + this.setState(prevState => ({ + touched: setIn(prevState.touched, field, true), + })); + + Eif (this.props.validateOnBlur) { + this.runValidations(this.state.values); + } + }; + + Iif (isString(eventOrString)) { + // cache these handlers by key like Preact's linkState does for perf boost + return isFunction(this.hbCache[eventOrString]) + ? this.hbCache[eventOrString] + : (this.hbCache[eventOrString] = (event: any) => + executeBlur(event, eventOrString)); + } else { + executeBlur(eventOrString); + } + }; + + setFieldTouched = ( + field: string, + Itouched: boolean = true, + EshouldValidate: boolean = true + ) => { + // Set touched field by name + this.setState( + prevState => ({ + ...prevState, + touched: setIn(prevState.touched, field, touched), + }), + () => { + if (this.props.validateOnBlur && shouldValidate) { + this.runValidations(this.state.values); + } + } + ); + }; + + setFieldError = (field: string, message: string) => { + // Set form field by name + this.setState(prevState => ({ + ...prevState, + errors: setIn(prevState.errors, field, message), + })); + }; + + resetForm = (nextValues?: Values) => { + const values = nextValues ? nextValues : this.props.initialValues; + + this.initialValues = values; + + this.setState({ + isSubmitting: false, + errors: {}, + touched: {}, + error: undefined, + status: undefined, + values, + submitCount: 0, + }); + Object.keys(this.fields).map(f => this.fields[f](values)); + }; + + handleReset = () => { + if (this.props.onReset) { + const maybePromisedOnReset = (this.props.onReset as any)( + this.state.values, + this.getFormikActions() + ); + + if (isPromise(maybePromisedOnReset)) { + (maybePromisedOnReset as Promise<any>).then(this.resetForm); + } else { + this.resetForm(); + } + } else { + this.resetForm(); + } + }; + + setFormikState = (s: any, callback?: (() => void)) => + this.setState(s, callback); + + getFormikActions = (): FormikActions<Values> => { + return { + resetForm: this.resetForm, + submitForm: this.submitForm, + validateForm: this.runValidations, + setError: this.setError, + setErrors: this.setErrors, + setFieldError: this.setFieldError, + setFieldTouched: this.setFieldTouched, + setFieldValue: this.setFieldValue, + setStatus: this.setStatus, + setSubmitting: this.setSubmitting, + setTouched: this.setTouched, + setValues: this.setValues, + setFormikState: this.setFormikState, + }; + }; + + getFormikComputedProps = () => { + const { isInitialValid } = this.props; + const dirty = !isEqual(this.initialValues, this.state.values); + return { + dirty, + isValid: dirty + ? this.state.errors && Object.keys(this.state.errors).length === 0 + : isInitialValid !== false && isFunction(isInitialValid) + ? (isInitialValid as (props: this['props']) => boolean)(this.props) + : (isInitialValid as boolean), + initialValues: this.initialValues, + }; + }; + + getFormikBag = () => { + return { + ...this.state, + ...this.getFormikActions(), + ...this.getFormikComputedProps(), + + // FastField needs to communicate with Formik during resets + registerField: this.registerField, + unregisterField: this.unregisterField, + handleBlur: this.handleBlur, + handleChange: this.handleChange, + handleReset: this.handleReset, + handleSubmit: this.handleSubmit, + validateOnChange: this.props.validateOnChange, + validateOnBlur: this.props.validateOnBlur, + }; + }; + + getFormikContext = (): FormikContext<any> => { + return { + ...this.getFormikBag(), + validationSchema: this.props.validationSchema, + validate: this.props.validate, + }; + }; + + render() { + const { component, render, children } = this.props; + const props = this.getFormikBag(); + const ctx = this.getFormikContext(); + return ( + <FormikProvider value={ctx}> + {component + ? React.createElement(component as any, props) + : render + ? (render as any)(props) + : children // children come last, always called + ? typeof children === 'function' + ? (children as any)(props) + : !isEmptyChildren(children) + ? React.Children.only(children) + : null + : null} + </FormikProvider> + ); + } +} + +function warnAboutMissingIdentifier({ + htmlContent, + documentationAnchorLink, + handlerName, +}: { + htmlContent: string; + documentationAnchorLink: string; + handlerName: string; +}) { + console.error( + `Warning: Formik called \`${handlerName}\`, but you forgot to pass an \`id\` or \`name\` attribute to your input: + + ${htmlContent} + + Formik cannot determine which value to update. For more info see https://github.com/jaredpalmer/formik#${documentationAnchorLink} + ` + ); +} + +/** + * Transform Yup ValidationError to a more usable object + */ +export function yupToFormErrors<Values>(yupError: any): FormikErrors<Values> { + let errors: any = {} as FormikErrors<Values>; + for (let err of yupError.inner) { + if (!errors[err.path]) { + errors = setIn(errors, err.path, err.message); + } + } + return errors; +} + +/** + * Validate a yup schema. + */ +export function validateYupSchema<T extends FormikValues>( + values: T, + schema: any, + Esync: boolean = false, + Econtext: any = {} +): Promise<Partial<T>> { + let validateData: Partial<T> = {}; + for (let k in values) { + Eif (values.hasOwnProperty(k)) { + const key = String(k); + validateData[key] = values[key] !== '' ? values[key] : undefined; + } + } + return schema[sync ? 'validateSync' : 'validate'](validateData, { + abortEarly: false, + context: context, + }); +} + |
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 | 5x + + + + + + + + + +5x + + + + + + + + +5x +5x +5x +5x +5x + +15x +15x +15x +15x + + + +15x +15x +15x + +15x +45x +45x + + + + + + + + + + + +15x + + + + + + + + + + + + + + + + + + + + + + + + + + | import { ComponentClass } from 'react'; + +const REACT_STATICS: any = { + childContextTypes: true, + contextTypes: true, + defaultProps: true, + displayName: true, + getDefaultProps: true, + mixins: true, + propTypes: true, + type: true, +}; + +const KNOWN_STATICS: any = { + name: true, + length: true, + prototype: true, + caller: true, + callee: true, + arguments: true, + arity: true, +}; + +const getOwnPropertySymbols = Object.getOwnPropertySymbols; +const propIsEnumerable = Object.prototype.propertyIsEnumerable; +consEt getPrototypeOf = Object.getPrototypeOf; +const obEjectPrototype = getPrototypeOf && getPrototypeOf(Object); +const getOwnPropertyNames = Object.getOwnPropertyNames; +I +export function hoistNonReactStatics<P>( + targetComponent: ComponentClass<P>, + sourceComponent: ComponentClass<any>, + blacklist?: { [name: string]: boolean } +): CompoEnentClass<P> { + if (typeof sourceComponent !== 'string') { + // don't hoist over string (html) components + + if (objectPrototype) { + let inIheritedComponent = getPrototypeOf(sourceComponent); + if (inheritedComponent && inheritedComponent !== objectPrototype) { + hoistNonReactStatics(targetComponent, inheritedComponent, blacklist); + } + } + + let keys = getOwnPropertyNames(sourceComponent); + + if (getOwnPropertySymbols) { + keys = keys.concat(getOwnPropertySymbols(sourceComponent) as any); + } + + for (let i = 0; i < keys.length; ++i) { + let key: string = keys[i]; + if ( + !REACT_STATICS[key] && + !KNOWN_STATICS[key] && + (!blacklist || !blacklist[key]) + ) { + // Only hoist enumerables and non-enumerable functions + if ( + propIsEnumerable.call(sourceComponent, key) || + typeof (sourceComponent as any)[key] === 'function' + ) { + try { + // Avoid failures from read-only properties + (targetComponent as any)[key] = (sourceComponent as any)[key]; + // tslint:disable-next-line:no-empty + } catch (e) {} + } + } + } + + return targetComponent; + } + + return targetComponent; +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
FastField.tsx | +63.93% | +78/122 | +60.71% | +34/56 | +29.17% | +7/24 | +64.42% | +67/104 | +|
Field.tsx | +95% | +57/60 | +94.44% | +34/36 | +75% | +6/8 | +93.88% | +46/49 | +|
FieldArray.tsx | +80.17% | +93/116 | +68.29% | +28/41 | +54.76% | +23/42 | +90.63% | +87/96 | +|
Form.tsx | +40% | +2/5 | +100% | +0/0 | +0% | +0/1 | +100% | +2/2 | +|
Formik.tsx | +87.13% | +176/202 | +69.74% | +106/152 | +79.25% | +42/53 | +87.17% | +163/187 | +|
batchUpdate.tsx | +62.5% | +5/8 | +50% | +2/4 | +0% | +0/1 | +71.43% | +5/7 | +|
connect.tsx | +100% | +7/7 | +100% | +0/0 | +100% | +3/3 | +100% | +6/6 | +|
utils.ts | +94.34% | +50/53 | +86.84% | +33/38 | +90.91% | +10/11 | +96.08% | +49/51 | +|
withFormik.tsx | +100% | +20/20 | +78.57% | +11/14 | +100% | +8/8 | +100% | +18/18 | +
1 +2 +3 +4 +5 +6 +7 +8 | + + + + + + + | export * from './Formik'; +export * from './Field'; +export * from './Form'; +export * from './withFormik'; +export * from './FieldArray'; +export * from './utils'; +export * from './FastField'; + |