├── .gitignore ├── .travis.yml ├── tsconfig.json ├── LICENSE ├── rollup.config.js ├── src └── formik-effect.tsx ├── tslint.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | compiled 4 | *.log 5 | coverage 6 | .DS_Store 7 | next.d.ts 8 | legacy.d.ts -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | script: 5 | - yarn test -- --runInBand --no-cache --coverage -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "declarationDir": "dist", 6 | "emitDecoratorMetadata": false, 7 | "experimentalDecorators": false, 8 | "importHelpers": true, 9 | "jsx": "react", 10 | "lib": ["es2015", "dom"], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitAny": true, 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "outDir": "compiled", 20 | "pretty": true, 21 | "removeComments": true, 22 | "sourceMap": true, 23 | "strict": true, 24 | "strictNullChecks": true, 25 | "stripInternal": true, 26 | "target": "es5", 27 | "typeRoots": ["node_modules/@types"] 28 | }, 29 | "include": ["src", "./typings.d.ts"], 30 | "exclude": ["node_modules", "dist"] 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jared Palmer http://jaredpalmer.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs'; 2 | import filesize from 'rollup-plugin-filesize'; 3 | 4 | import resolve from 'rollup-plugin-node-resolve'; 5 | import sourceMaps from 'rollup-plugin-sourcemaps'; 6 | import uglify from 'rollup-plugin-uglify'; 7 | 8 | const shared = { 9 | entry: `compiled/formik-effect.js`, 10 | sourceMap: true, 11 | external: ['react', 'formik', 'prop-types'], 12 | globals: { 13 | react: 'React', 14 | formik: 'formik', 15 | 'prop-types': 'PropTypes', 16 | }, 17 | exports: 'named', 18 | }; 19 | 20 | export default [ 21 | Object.assign({}, shared, { 22 | moduleName: 'FormikEffect', 23 | format: 'umd', 24 | dest: 'dist/formik-effect.umd.js', 25 | plugins: [ 26 | resolve(), 27 | commonjs({ 28 | include: /node_modules/, 29 | }), 30 | sourceMaps(), 31 | filesize(), 32 | uglify(), 33 | ], 34 | }), 35 | 36 | Object.assign({}, shared, { 37 | targets: [ 38 | { dest: 'dist/formik-effect.es6.js', format: 'es' }, 39 | { dest: 'dist/formik-effect.js', format: 'cjs' }, 40 | ], 41 | plugins: [ 42 | resolve(), 43 | commonjs({ 44 | include: /node_modules/, 45 | }), 46 | sourceMaps(), 47 | ], 48 | }), 49 | ]; 50 | -------------------------------------------------------------------------------- /src/formik-effect.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FormikProps, FormikState } from 'formik'; 3 | import * as PropTypes from 'prop-types'; 4 | 5 | export interface EffectProps { 6 | onChange( 7 | currentState: FormikState, 8 | nextState: FormikState 9 | ): void; 10 | } 11 | 12 | export class Effect extends React.Component< 13 | EffectProps, 14 | {} 15 | > { 16 | static contextTypes = { 17 | formik: PropTypes.object, 18 | }; 19 | 20 | componentWillReceiveProps( 21 | _nextProps: EffectProps, 22 | nextContext: { formik: FormikProps } 23 | ) { 24 | const { values, touched, errors, isSubmitting } = this.context.formik; 25 | const { 26 | values: nextValues, 27 | touched: nextTouched, 28 | errors: nextErrors, 29 | isSubmitting: nextIsSubmitting, 30 | } = nextContext.formik; 31 | if (nextContext.formik !== this.context.formik) { 32 | this.props.onChange( 33 | { 34 | values, 35 | touched, 36 | errors, 37 | isSubmitting, 38 | }, 39 | { 40 | values: nextValues, 41 | touched: nextTouched, 42 | errors: nextErrors, 43 | isSubmitting: nextIsSubmitting, 44 | } 45 | ); 46 | } 47 | } 48 | 49 | render() { 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-react"], 3 | "rules": { 4 | "jsx-wrap-multiline": false, 5 | "align": [true, "parameters", "statements"], 6 | "ban": false, 7 | "class-name": true, 8 | "comment-format": [true, "check-space"], 9 | "curly": true, 10 | "eofline": false, 11 | "forin": true, 12 | "indent": [true, "spaces"], 13 | "interface-name": [true, "never-prefix"], 14 | "jsdoc-format": true, 15 | "jsx-no-lambda": false, 16 | "jsx-no-multiline-js": false, 17 | "label-position": true, 18 | "max-line-length": [false, 120], 19 | "member-ordering": [ 20 | true, 21 | "public-before-private", 22 | "static-before-instance", 23 | "variables-before-functions" 24 | ], 25 | 26 | "no-arg": true, 27 | "no-bitwise": true, 28 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 29 | "no-consecutive-blank-lines": true, 30 | "no-construct": true, 31 | "no-debugger": true, 32 | "no-duplicate-variable": true, 33 | "no-empty": true, 34 | "no-eval": true, 35 | "no-string-literal": true, 36 | "no-switch-case-fall-through": true, 37 | "no-trailing-whitespace": false, 38 | "no-unused-expression": true, 39 | "no-use-before-declare": true, 40 | "one-line": [ 41 | true, 42 | "check-catch", 43 | "check-else", 44 | "check-open-brace", 45 | "check-whitespace" 46 | ], 47 | "quotemark": [true, "single", "jsx-double"], 48 | "radix": true, 49 | "semicolon": [false, "always"], 50 | "switch-default": true, 51 | "trailing-comma": false, 52 | "triple-equals": [true, "allow-null-check"], 53 | "typedef": [true, "parameter", "property-declaration"], 54 | "typedef-whitespace": [ 55 | true, 56 | { 57 | "call-signature": "nospace", 58 | "index-signature": "nospace", 59 | "parameter": "nospace", 60 | "property-declaration": "nospace", 61 | "variable-declaration": "nospace" 62 | } 63 | ], 64 | "variable-name": [ 65 | true, 66 | "ban-keywords", 67 | "check-format", 68 | "allow-leading-underscore", 69 | "allow-pascal-case" 70 | ], 71 | "whitespace": [ 72 | true, 73 | "check-branch", 74 | "check-decl", 75 | "check-module", 76 | "check-operator", 77 | "check-separator", 78 | "check-type", 79 | "check-typecast" 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik-effect", 3 | "description": "Declarative effects for Formik forms", 4 | "version": "1.2.0", 5 | "license": "MIT", 6 | "author": "Jared Palmer ", 7 | "repository": "jaredpalmer/formik-effect", 8 | "keywords": [ 9 | "formik", 10 | "react", 11 | "react-dom", 12 | "form", 13 | "validation", 14 | "forms", 15 | "localstorage", 16 | "hoc" 17 | ], 18 | "main": "dist/formik-effect.js", 19 | "module": "dist/formik-effect.es6.js", 20 | "typings": "dist/formik-effect.d.ts", 21 | "files": ["dist"], 22 | "scripts": { 23 | "start": "cross-env NODE_ENV=development tsc-watch --onSuccess \"rollup -c\"", 24 | "prebuild": "rimraf dist", 25 | "build": "tsc && cross-env NODE_ENV=production rollup -c && rimraf compiled", 26 | "prepublish": "npm run build", 27 | "format": "prettier --trailing-comma es5 --single-quote --write 'src/**/*' 'test/**/*'", 28 | "precommit": "lint-staged", 29 | "addc": "all-contributors add", 30 | "gen-docs": "all-contributors generate && doctoc README.md" 31 | }, 32 | "dependencies": {}, 33 | "peerDependencies": { 34 | "formik": "*", 35 | "react": ">=15", 36 | "prop-types": "^15.5.10" 37 | }, 38 | "resolutions": { 39 | "**/@types/react": "16.0.28", 40 | "**/@types/react-dom": "16.0.3" 41 | }, 42 | "optionalDependencies": {}, 43 | "devDependencies": { 44 | "@types/jest": "20.0.6", 45 | "@types/node": "8.0.19", 46 | "@types/prop-types": "15.5.1", 47 | "@types/react": "16.0.28", 48 | "@types/react-dom": "16.0.3", 49 | "all-contributors-cli": "^4.4.0", 50 | "cross-env": "5.0.5", 51 | "doctoc": "^1.3.0", 52 | "formik": "*", 53 | "husky": "0.14.3", 54 | "jest": "20.0.4", 55 | "lint-staged": "4.0.2", 56 | "prettier": "1.5.3", 57 | "react": "^16.2.0", 58 | "react-dom": "^16.2.0", 59 | "rimraf": "2.6.1", 60 | "rollup": "0.45.2", 61 | "rollup-plugin-commonjs": "8.1.0", 62 | "rollup-plugin-filesize": "1.4.2", 63 | "rollup-plugin-node-resolve": "3.0.0", 64 | "rollup-plugin-replace": "1.1.1", 65 | "rollup-plugin-sourcemaps": "0.4.2", 66 | "rollup-plugin-uglify": "2.0.1", 67 | "ts-jest": "20.0.9", 68 | "tsc-watch": "1.0.7", 69 | "tslint": "5.5.0", 70 | "tslint-react": "3.2.0", 71 | "typescript": "2.7.2", 72 | "yup": "0.21.3" 73 | }, 74 | "lint-staged": { 75 | "**/*.{ts,tsx}": [ 76 | "prettier --trailing-comma es5 --single-quote --write", 77 | "git add" 78 | ] 79 | }, 80 | "prettier": { 81 | "trailingComma": "es5", 82 | "singleQuote": true, 83 | "semi": true 84 | }, 85 | "jest": { 86 | "collectCoverageFrom": ["src/**/*.{ts,tsx}"], 87 | "transform": { 88 | ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js" 89 | }, 90 | "testMatch": [ 91 | "/test/**/*.ts?(x)", 92 | "/test/**/?(*.)(spec|test).ts?(x)" 93 | ], 94 | "transformIgnorePatterns": ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"], 95 | "moduleFileExtensions": ["ts", "tsx", "js", "json"] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formik Effect 2 | 3 | Declarative Formik component for side-effects. Use with caution. Please. I. beg. you. 4 | 5 | ``` 6 | npm install formik-effect --save 7 | ``` 8 | 9 | **Note: this has peer dependencies of `prop-types`, `react`, and `formik` (obvs)** 10 | 11 | ## The Problem 12 | 13 | Formik is an uncontrolled component. However, there are times when you want to trigger a side effect based on a state change. By design, Formik does not expose a prop for you to do this because I'm terrified as to how it would be abused--it encourages people to attempt to "sync" Formik's state in elsewhere (cough like in a Redux store cough cough). Anyways, please don't do that. You never ever ever ever want to store the same state in 2 places in a React application because it is almost impossible to keep them in sync unless you are a goddamn jedi. You may think it's working, and high five a teammate, but you are a just a lifecycle method away from lots and lots of pain and I can guarantee you are not considering all the edge cases. Sooooo.... 14 | 15 | **SAY IT WITH ME:** 16 | 17 | **"I WILL NOT USE THIS TO STORE FORMIK STATE ELSEWHERE IN MY APP."** 18 | **"I WILL NOT USE THIS TO STORE FORMIK STATE ELSEWHERE IN MY APP."** 19 | **"I WILL NOT USE THIS TO STORE FORMIK STATE ELSEWHERE IN MY APP."** 20 | 21 | ## Basic Usage 22 | 23 | Import the `` component and put it inside any Formik form. It renders `null`! Pass it an `onChange()` function and it will be called whenever your Formik form's state updates. 24 | 25 | ```js 26 | import React from 'react' 27 | import { Formik, Field, Form } from 'formik' 28 | import { Effect } from 'formik-effect' 29 | 30 | export const Signup = () => 31 |
32 |

My Cool Form with a SideEffect

33 | console.log(values)} 35 | initialValues={{ firstName: '', lastName: '', email: '' }} 36 | render={props => 37 |
38 | { 39 | // do whatevs 40 | // FYI if you alter state it will cause another render 41 | }} 42 | /> 43 | 44 | 45 | 46 | 47 | 48 | } 49 | /> 50 |
; 51 | ``` 52 | 53 | ### API 54 | 55 | Only one! 56 | 57 | 58 | #### `onChange: (currentState: FormikState, nextState: FormikState => void` 59 | 60 | Put your side effect here. 61 | 62 | `FormikState` includes: 63 | 64 | - `values` 65 | - `errors` 66 | - `touched` 67 | - `isSubmitting` 68 | 69 | Under the hood this calls `componentWillReceiveProps()`. When Formik refactors for React 16.3, it will use `componentDidUpdate`. Either way, it does shallow comparison on context with triple equals, which may not be what you want. Luckily, this whole component is like 500 bytes so you could just copy pasta it into your app. 70 | 71 | ## Future Work 72 | 73 | When Formik is updated to React 16.3, this library will need to be updated for use without PropTypes. 74 | 75 | ## Author 76 | 77 | - Jared Palmer [@jaredpalmer](https://twitter.com/jaredpalmer) 78 | --------------------------------------------------------------------------------