Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* Close jaredpalmer#200. Add Field level validation

* Regenerate doctoc

* Remove handleChangeValue completely

* v0.11.0-alpha.1

* Add missing name in call to setFieldError (jaredpalmer#290)

* v0.11.0-alpha.2

* jaredpalmer#281 Add array helpers MVP, update ts setup for better DX (jaredpalmer#298)

* v0.11.0-alpha.3

* Actually export FieldArray

* v0.11.0-alpha.4

* Upgrade to React 16, TypeScript 2.6.2 (jaredpalmer#300)

* jaredpalmer#283 Update to React 16, TypeScript 2.6.2

* Update Travis build to use npm instead of yarn

* Move back to yarn (jaredpalmer#301)

* Fix travis script to use yarn.

* Add form to TS types of FieldArray

* v0.11.0-beta.1

* Close jaredpalmer#281 jaredpalmer#155. Add docs about array helpers

* Add field array examples and update toc

* Fix unshift in FieldArray (jaredpalmer#337)

* Fix FieldArray component prop (jaredpalmer#341)

* Fix FieldArray component prop

* nit: test names [skip ci]

* Allow bracket path support (jaredpalmer#334)

* feat(Field): Allow bracket path support

* chore(deps): Add types for lodash.topath

* chore(deps): yarn.lock

* Fix jaredpalmer#280. Fix the definition dirty and isValid (jaredpalmer#365)

* Fix jaredpalmer#280. Change the meaning of dirty and isValid

* Lock in to path dep

* Update tests for new definition of dirty

* Update docs

* Fixed typo in FieldArray code (jaredpalmer#380)

* Fix setDeep mutations with nested values (jaredpalmer#373)

* Fix start task

* Get rid of mutators file

* v0.11.0-rc.1

* jaredpalmer#285 jaredpalmer#122 Add example multistep / form wizard

* jaredpalmer#378 remove setFieldTouched from FieldArray

* v0.11.0-rc.2

* jaredpalmer#223 Fix checkbox validation error (again)

* Add onReset callback. (jaredpalmer#328)

* feat(formik): Add onReset callback.

* feat(formik): Use promise sniffing for onReset prop.

This passes returned promise data to resetForm, allowing the promise to add new nextValues.
  • Loading branch information
jaredpalmer authored Feb 1, 2018
1 parent 6491acd commit b34fe0a
Show file tree
Hide file tree
Showing 20 changed files with 2,555 additions and 1,071 deletions.
403 changes: 303 additions & 100 deletions README.md

Large diffs are not rendered by default.

170 changes: 170 additions & 0 deletions examples/MultistepWizard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from 'react';
import { Formik, Field } from 'formik';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const required = value => (value ? undefined : 'Required');

const Error = ({ name }) => (
<Field
name={name}
render={({ form: { touched, errors } }) =>
touched[name] && errors[name] ? <span>{errors[name]}</span> : null
}
/>
);

class Wizard extends React.Component {
static Page = ({ children }) => children;

constructor(props) {
super(props);
this.state = {
page: 0,
values: props.initialValues,
};
}

next = values =>
this.setState(state => ({
page: Math.min(state.page + 1, this.props.children.length - 1),
values,
}));

previous = () =>
this.setState(state => ({
page: Math.max(state.page - 1, 0),
}));

validate = values => {
const activePage = React.Children.toArray(this.props.children)[
this.state.page
];
return activePage.props.validate ? activePage.props.validate(values) : {};
};

handleSubmit = (values, bag) => {
const { children, onSubmit } = this.props;
const { page } = this.state;
const isLastPage = page === React.Children.count(children) - 1;
if (isLastPage) {
return onSubmit(values);
} else {
this.next(values);
bag.setSubmitting(false);
}
};

render() {
const { children } = this.props;
const { page, values } = this.state;
const activePage = React.Children.toArray(children)[page];
const isLastPage = page === React.Children.count(children) - 1;
return (
<Formik
initialValues={values}
enableReinitialize={false}
onSubmit={this.handleSubmit}
render={({ values, handleSubmit, isSubmitting, handleReset }) => (
<form onSubmit={handleSubmit}>
{activePage}
<div className="buttons">
{page > 0 && (
<button type="button" onClick={this.previous}>
« Previous
</button>
)}

{!isLastPage && <button type="submit">Next »</button>}
{isLastPage && (
<button type="submit" disabled={isSubmitting}>
Submit
</button>
)}
</div>

<pre>{JSON.stringify(values, null, 2)}</pre>
</form>
)}
/>
);
}
}

const App = () => (
<div className="App">
<h1>Multistep / Form Wizard </h1>
<Wizard
initialValues={{
firstName: '',
lastName: '',
email: '',
favoriteColor: '',
}}
onSubmit={(values, actions) => {
sleep(300).then(() => {
window.alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
});
}}
>
<Wizard.Page>
<div>
<label>First Name</label>
<Field
name="firstName"
component="input"
type="text"
placeholder="First Name"
validate={required}
/>
<Error name="firstName" />
</div>
<div>
<label>Last Name</label>
<Field
name="lastName"
component="input"
type="text"
placeholder="Last Name"
validate={required}
/>
<Error name="lastName" />
</div>
</Wizard.Page>
<Wizard.Page
validate={values => {
const errors = {};
if (!values.email) {
errors.email = 'Required';
}
if (!values.favoriteColor) {
errors.favoriteColor = 'Required';
}
return errors;
}}
>
<div>
<label>Email</label>
<Field
name="email"
component="input"
type="email"
placeholder="Email"
/>
<Error name="email" />
</div>
<div>
<label>Favorite Color</label>
<Field name="favoriteColor" component="select">
<option />
<option value="#ff0000">❤️ Red</option>
<option value="#00ff00">💚 Green</option>
<option value="#0000ff">💙 Blue</option>
</Field>
<Error name="favoriteColor" />
</div>
</Wizard.Page>
</Wizard>
</div>
);
37 changes: 23 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "formik",
"description": "Forms in React, without tears",
"version": "0.10.5",
"version": "0.11.0-rc.2",
"license": "MIT",
"author": "Jared Palmer <[email protected]>",
"repository": "jaredpalmer/formik",
Expand All @@ -24,17 +24,19 @@
"scripts": {
"test": "jest --env=jsdom",
"test:watch": "npm run test -- --watch",
"start": "cross-env NODE_ENV=development tsc-watch --onSuccess \"rollup -c\"",
"start": "cross-env NODE_ENV=development tsc-watch --project tsconfig.base.json --onSuccess \"rollup -c\"",
"prebuild": "rimraf dist",
"build": "tsc && cross-env NODE_ENV=production rollup -c && cross-env NODE_ENV=development rollup -c && rimraf compiled",
"build": "tsc -p tsconfig.base.json && cross-env NODE_ENV=production rollup -c && cross-env NODE_ENV=development rollup -c && rimraf compiled",
"prepublish": "npm run build",
"format": "prettier --trailing-comma es5 --single-quote --write 'src/**/*' 'test/**/*' 'README.md'",
"precommit": "lint-staged",
"addc": "all-contributors add",
"gen-docs": "all-contributors generate && doctoc README.md"
},
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "4.5.0",
"lodash.topath": "4.5.2",
"prop-types": "^15.5.10",
"warning": "^3.0.0"
},
Expand All @@ -43,26 +45,30 @@
},
"optionalDependencies": {},
"devDependencies": {
"@types/enzyme": "2.8.4",
"@types/enzyme": "3.1.5",
"@types/enzyme-adapter-react-16": "1.0.1",
"@types/jest": "20.0.6",
"@types/lodash.clonedeep": "^4.5.3",
"@types/lodash.isequal": "4.5.2",
"@types/lodash.topath": "4.5.3",
"@types/node": "8.0.19",
"@types/prop-types": "15.5.1",
"@types/react": "16.0.0",
"@types/react-dom": "15.5.1",
"@types/react": "16.0.28",
"@types/react-dom": "16.0.3",
"@types/react-test-renderer": "15.5.2",
"@types/warning": "^3.0.0",
"all-contributors-cli": "^4.4.0",
"cross-env": "5.0.5",
"doctoc": "^1.3.0",
"enzyme": "2.9.1",
"enzyme": "3.2.0",
"enzyme-adapter-react-16": "^1.1.0",
"husky": "0.14.3",
"jest": "20.0.4",
"jest": "^21.2.1",
"jest-cli": "^21.2.1",
"lint-staged": "4.0.2",
"prettier": "^1.8.2",
"react": "15.6.1",
"react-dom": "15.6.1",
"react-test-renderer": "15.6.1",
"react": "16.2.0",
"react-dom": "16.2.0",
"rimraf": "2.6.1",
"rollup": "0.45.2",
"rollup-plugin-commonjs": "8.1.0",
Expand All @@ -71,11 +77,11 @@
"rollup-plugin-replace": "1.1.1",
"rollup-plugin-sourcemaps": "0.4.2",
"rollup-plugin-uglify": "2.0.1",
"ts-jest": "20.0.9",
"ts-jest": "^21.2.4",
"tsc-watch": "1.0.7",
"tslint": "5.5.0",
"tslint-react": "3.2.0",
"typescript": "2.4.2",
"typescript": "2.6.2",
"yup": "0.21.3"
},
"lint-staged": {
Expand All @@ -90,14 +96,17 @@
"semi": true
},
"jest": {
"setupFiles": [
"raf/polyfill",
"<rootDir>/test/setupTest.ts"
],
"collectCoverageFrom": [
"src/**/*.{ts,tsx}"
],
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testMatch": [
"<rootDir>/test/**/*.ts?(x)",
"<rootDir>/test/**/?(*.)(spec|test).ts?(x)"
],
"transformIgnorePatterns": [
Expand Down
4 changes: 1 addition & 3 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export default [
process.env.NODE_ENV || 'development'
),
}),
resolve(),
commonjs({
include: /node_modules/,
namedExports: {
Expand Down Expand Up @@ -74,8 +73,7 @@ export default [
],
},
}),
,
sourceMaps(),
],
}),
];
];
59 changes: 52 additions & 7 deletions src/Field.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as PropTypes from 'prop-types';
import * as React from 'react';
import { dlv } from './utils';
import { dlv, isPromise } from './utils';

import { FormikProps } from './formik';
import { isFunction, isEmptyChildren } from './utils';
Expand Down Expand Up @@ -60,6 +60,11 @@ export interface FieldConfig {
*/
children?: ((props: FieldProps<any>) => React.ReactNode);

/**
* Validate a single field value independently
*/
validate?: ((value: any) => string | Function | Promise<void> | undefined);

/**
* Field name
*/
Expand Down Expand Up @@ -92,6 +97,7 @@ export class Field<Props extends FieldAttributes = any> extends React.Component<
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
validate: PropTypes.func,
};

componentWillMount() {
Expand All @@ -103,7 +109,7 @@ export class Field<Props extends FieldAttributes = any> extends React.Component<
);

warning(
!(component && children && isFunction(children)),
!(this.props.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.'
);

Expand All @@ -113,19 +119,58 @@ export class Field<Props extends FieldAttributes = any> extends React.Component<
);
}

handleChange = (e: React.ChangeEvent<any>) => {
const { handleChange, validateOnChange } = this.context.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.context.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.context.formik;
const { name, validate } = this.props;
// Call validate fn
const maybePromise = (validate as any)(value);
// Check if validate it returns a Promise
if (isPromise(maybePromise)) {
(maybePromise as Promise<any>).then(
() => setFieldError(name, undefined),
error => setFieldError(name, error)
);
} else {
// Otherwise set the error
setFieldError(name, maybePromise);
}
};

render() {
const { name, render, children, component = 'input', ...props } = this
.props as FieldConfig;
const {
validate,
name,
render,
children,
component = 'input',
...props
} = this.props as FieldConfig;

const { formik } = this.context;
const field = {
value:
props.type === 'radio' || props.type === 'checkbox'
? props.value
? props.value // React uses checked={} for these inputs
: dlv(formik.values, name),
name,
onChange: formik.handleChange,
onBlur: formik.handleBlur,
onChange: validate ? this.handleChange : formik.handleChange,
onBlur: validate ? this.handleBlur : formik.handleBlur,
};
const bag = { field, form: formik };

Expand Down
Loading

0 comments on commit b34fe0a

Please sign in to comment.