Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next #276

Merged
merged 30 commits into from
Feb 1, 2018
Merged

Next #276

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
97e7b44
Close #200. Add Field level validation
jaredpalmer Nov 28, 2017
55565e4
Regenerate doctoc
jaredpalmer Nov 28, 2017
e9d0b10
Remove handleChangeValue completely
jaredpalmer Nov 28, 2017
77a777d
v0.11.0-alpha.1
jaredpalmer Nov 28, 2017
2783e26
Add missing name in call to setFieldError (#290)
LinusU Dec 5, 2017
725d6bd
v0.11.0-alpha.2
jaredpalmer Dec 5, 2017
74e7fb0
#281 Add array helpers MVP, update ts setup for better DX (#298)
jaredpalmer Dec 10, 2017
5387fdb
v0.11.0-alpha.3
jaredpalmer Dec 10, 2017
e26eb40
Actually export FieldArray
jaredpalmer Dec 10, 2017
eaed294
v0.11.0-alpha.4
jaredpalmer Dec 10, 2017
16509e5
Upgrade to React 16, TypeScript 2.6.2 (#300)
jaredpalmer Dec 11, 2017
d4fcb76
Add form to TS types of FieldArray
jaredpalmer Dec 13, 2017
37a185f
v0.11.0-beta.1
jaredpalmer Dec 14, 2017
45cd9c7
Close #281 #155. Add docs about array helpers
jaredpalmer Dec 29, 2017
1dea875
Add field array examples and update toc
jaredpalmer Dec 29, 2017
fcf969d
Fix unshift in FieldArray (#337)
existentialism Jan 10, 2018
548b296
Fix FieldArray component prop (#341)
existentialism Jan 12, 2018
560a5e3
Allow bracket path support (#334)
Jan 17, 2018
343b9c9
Fix #280. Fix the definition dirty and isValid (#365)
jaredpalmer Jan 23, 2018
e65f9ca
Fixed typo in FieldArray code (#380)
Jan 26, 2018
1b5a5b1
Fix setDeep mutations with nested values (#373)
ecoleman Jan 26, 2018
a21636a
Merge branch 'master' into next
jaredpalmer Jan 29, 2018
7afcc43
Fix start task
jaredpalmer Jan 29, 2018
67ef45f
Get rid of mutators file
jaredpalmer Jan 29, 2018
f3492a6
v0.11.0-rc.1
jaredpalmer Jan 29, 2018
7c8753e
#285 #122 Add example multistep / form wizard
jaredpalmer Jan 29, 2018
b32ebfd
#378 remove setFieldTouched from FieldArray
jaredpalmer Jan 29, 2018
66cf22a
v0.11.0-rc.2
jaredpalmer Jan 29, 2018
f076355
#223 Fix checkbox validation error (again)
jaredpalmer Jan 30, 2018
5017053
Add onReset callback. (#328)
Feb 1, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <jared@palmer.net>",
"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