{"_id":"effector-forms","_rev":"51-8df63f026953160c9b14b147f8f82c2e","name":"effector-forms","dist-tags":{"latest":"1.3.4","alpha":"0.0.23-alpha.10","beta":"1.1.0-beta","next":"2.0.0-next.1"},"versions":{"0.0.1":{"name":"effector-forms","version":"0.0.1","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-form.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-form.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-form/issues"},"homepage":"https://github.com/aanation/effector-form#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"2f19dcc65e5a8c5b78fa93dba5ccd1130e3be829","_id":"effector-forms@0.0.1","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-0uT3/Os7kHP3QKx8sEFiHKKdpWtieMDNR/7UFQLl1ygTmGeFQuLgAuz4MHwjAEv9wMmWI+3rH8h1OiFQS1xZzA==","shasum":"fc03e929a46d1e8776b448ef399ed1b319f4d09f","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.1.tgz","fileCount":20,"unpackedSize":114442,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfN815CRA9TVsSAnZWagAAEqkP/jcVQtJxe/5Y/PmIdd6I\nFPyieJxZb70P1w1RR/eh//ZTpzFfJZls/RT6SLFRvsvk5zvDWBTmZVtZAPYR\nSUUdA3xH9mPuq7yfukLLwf6P3b3/0MZAb3U/ywBEXAjQxjLfCjwg+zJS8Q2i\nJloaepBaPlSCebzZYjSQA0HqOdk34aEKYGK1kaTH4CYyETiDDpN13nqC+Q3s\nwtCI3iGwaxdLUmZVngNQz2RTg5n+v8aV1C9LjalXj9QXFgY5R6I6arMuhoWi\nyuNMfknCj5g9c0LiwZFej+oFDzR+fapDhgulwQQ6GAkUHt/zkcczouTfQ5wi\nKNmJzZ+QgnlgNn8k0+djxXyNMF/qiMcOkeZqbJN2qfGAqAvwGmc6+xApADmS\nexfTse+hsPIEH+54O7ht2+aBNOsrMao1QtRfTvEuA7tD9+nDAx/jd+Fc3t46\n+EPOSoKqZJ1r+/Z2pU9MpYsBYZey0bvyXQaTauX9baU547ADXpCMVuyUiKXY\n82R7G0ax0ukRVDp654jbO0428m8CGrS3/LLA6oOh6xsQrsatKvQOPB9LFxSl\n6foE4OMM4Ndbe6tsTfNYiSkFwtF9lG4QXGWgN6Fz+O0g8qP22BT21qaHDRCC\ntG0Zd5P/eHwK5D688LwLhWvgT/CYZPS6EhvlwyH8f1lS6MpXvzab3hNgZaR6\nJLrt\r\n=suba\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGJ7G584eoGLIyM/oLJtrCStR54mARlw5jw+vpZtGuwnAiEApE/HWx+Lt8rpghJtSl5VXS2Q6GYt1KkH7iMxekCvYFk="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.1_1597492600596_0.30507990224987047"},"_hasShrinkwrap":false},"0.0.2":{"name":"effector-forms","version":"0.0.2","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-form.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"40bbb4a1138e4bbdac27d40db9fd0673d4549540","_id":"effector-forms@0.0.2","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-5bUsVZUkfyu3/JgUiwLNJXQOjXN987x7ji4L/0XwhGi3Y9LGJQ68LqC02KkNTnzFtuwQelKcMZheVoN/m96GHA==","shasum":"b21ac9fa019111e8469b223e50503373ae25e29d","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.2.tgz","fileCount":20,"unpackedSize":114445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfN86NCRA9TVsSAnZWagAA4ZYP/AmNblKSS5JUl6enVHva\n8scfPJKyRwFSpBb4Fa+cQMHsNIEv0OyvQhj/I+l4M1BvWrT+kg4S5eCvMZl/\nKgGznzfGnswGaLr5Oe5rmlk8o/R4Ocs8YCQL4B/7wpc8xpajdjkf1s/8wulI\nD4FPkjfPxWFxp4AKXKk/RNFzAV9pqDdmt5cTSzpMbaf+ZJFhyuFQGDUD2R/W\nEd9HiMlBToIGOuBGjlC9Z5QM9Xw/QSNBt8x0WUXU2lImXyfLzeExD/kzQg8M\n8i7etaO9lwxycmA+CNzMSFV3C60yqzUHVnmq3zJpzTwe7Ez1aEpnne8Tmjau\nEqo262UD582GMJjGCsYK3cmppDpbld8FLAlxRDIIxRTjsLYwcGDF2NCb1hYq\n+kqsuaAjMxqy1EjOiNFzDRQtup/hr7lyV9bxKYtZgJCwUYfMq4wsa5ObhHgU\nd7FQeCrS6n5HvVmjPT1zjeiIoXCfxrCRekwxOFstUAdrrng9hXoN1Q01eTmo\ncyaaUOYWl3ufeUuNtGpk0kLVKwPXNOlvm/PgAfmaUhmqhVGZxWdijXJtP0GL\nQZarGvJhG0qq/ttCVojnWsph2cQ2A0FIxMiyrlo2AcpT0R4T8KWxk4JDHG1R\nFf+aQ1/wCclQopboK24Zz8vYB+s0EqSeiRN9sWRaSZjl2nE95zDbR5RwGSd9\nXmTn\r\n=RX+S\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIH42ooBYREV96ntCNsvRfGwgLFY3f7YqIrH9lKnmJOZ8AiAKD1+91xXZGXh24yAHYOB0GZZNJEBI/BOHD932JM383Q=="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.2_1597492876957_0.8498429015548075"},"_hasShrinkwrap":false},"0.0.5":{"name":"effector-forms","version":"0.0.5","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-forms.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"a9950b781d44a4a600cf14e8059b7f7fe2dc0d55","_id":"effector-forms@0.0.5","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-BQjaN5Up9UkI+0o3YEFyrqi8Z5Pq24+E6/VR5edH7RYOlbQEcB8y9xart6L/ZGc3gnYeblcFsDAGF73by2ZxGA==","shasum":"a5cac1d7cd4501baf6a465982ba61829dfa5f552","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.5.tgz","fileCount":20,"unpackedSize":114446,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfOoNPCRA9TVsSAnZWagAAqHgP/0QGInrHEfWzdnK4CoYR\nX4DdUxT64MM7CBQtJ4rnZ25oxfddqr0Q3jXopWVrs3aEutK0ZSV+A3GEbFiX\n/MSFVvJ5PM/mkL0ZMWFWMaTM0RlkXLknQ/owZ9UtKqgU9RdWzjRzavJ1No9i\nxbMjyM0p1m6y8eeIB9NCeTOaH9IBzAL+3C2AZPP1csv7hqsABAJTfjv54ajZ\nvqYCMqqBlpCwiNgcm0bSj15XxhWtbId6/y1NEwTuU5UM4pyBh7mQ8nHk5eEk\nZsTNMO0ovSuTGEJMBAHdg/HmJnB5Gi/UiE9AWJJl0iiY41DosdIVHbjBRkyw\nuzDFJKCKYK3taplkCz3WERyA4s8J926kMZdvHhb7+ud2p+4cASosxhofvSVS\nkqyjNyKBvbbckjoTOXbe4veEjvYLHWAacDLFceobl07/1eGTO+geJVQIehEA\nME7wgCDF5HlYT4dO3dUiJ52LeLkuwBSXckQZC+i6W/re3dwpsOH+zNDSROPL\n9JoJXEwMulUHb7/EHuv+Otz39GhXuw6KK0CWnCV2HUr9XoQSbUZflqzyczK3\nAxyjaRFQ86lmabDWjuIvufFiu3D277a8eNsHW6Cdmc0kUh7DM1flHaE63JiL\nydN+3E168K8jI3wil7pTZr0jSEOcsO3RKNIjFlUJhy4AWmUEBqT6+rmg0fsu\nzXG8\r\n=LGip\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCWPdVyBBGKNQDuVWgDUBxTrerHaSeF5EyTIrUz9bUlqAIgWUNb7lNEjyYAmC76z8DtPjnPecE5jylErMt5gzbcDF0="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.5_1597670222935_0.5105002424976532"},"_hasShrinkwrap":false},"0.0.6":{"name":"effector-forms","version":"0.0.6","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-forms.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"1eeb556890fdae8fff0e2c6ca2fbf4debc9669ab","_id":"effector-forms@0.0.6","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-hzK6g/KWxN13x6fTuli6hCUYpcL0Kr0rMiq1uyungq2n67zPk+5xYhESTrIkSLSPaNvw3bbTw8YBkpiIRDewdw==","shasum":"35cea557ec427c4c21a000dec2ac1c74d5db084e","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.6.tgz","fileCount":20,"unpackedSize":123438,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfRA2CCRA9TVsSAnZWagAA9lQP/0Z5A9xwq11Ka95eHAaE\nknGW1ymxQJd1QuZNRQQkvwMQ5ArqM+a/POqA1P6ReLi74UmB7xW0A7uZa1Ph\nfUqJiyEHhDjEDWYgasWmbx5MMzjDj57iTPLuRxtSF4oF3LYvHJleNbNsvzVP\nUSYyIfiNqZ47MLHmGd8ACO5w4UiOQnP7n79D5hkTrD7YCJR5cl/oPvM0lTNr\nz5viN5MIioWTwI6Cn7tJqEEV0is3MoBQaT9FByYWYkAff7UxozeQxg++ehfJ\noJmFD6tMMumG+E+Z/BPwY4G5QX8zLTk4p939/mL6fz9wsNhbPT2CfV2qMqCk\nc15hOdg6Xu0WuAVCk4Y2tje40KbZPVWeSJIvRhTfi5bL39NVYWv5Jq1ZaSLB\n5Ar9LogThVn8AKF3JRWrLPJ2gr5H+ke24lEjLZSyg+l/1tn5IAUzO9E5dpHR\nO6V7U7bAzqMQbo9uuToaaFI5yXFEBPByr0lUgNRSdEnOPpweewNjZovEWyj0\nsSTen6373AssF5ZoG7Suryws9YSsdiJ4naVcigCVaBjf76QSPEqfS7k6rwqK\nwwz5VD/Zrbrl7ts+6beP1wnIu3jbNf3/xT9HE4rGarhLzNUzsbNaP/PsoAqe\n2IMnS+SzlbhFS/phlOykr58S6uR1nPBzA/WEpyldMSl7rFI2iTW5UK7D4bbs\nJnRH\r\n=4bGg\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICr0odOzImCUOJ/EjHa9UUdGerWXDG9Po92htG0pMr1MAiAyAnpLwMTBAbz5pcgEh0X3XqPk/mgAmxTKV8xwgagOuQ=="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.6_1598295425578_0.9902743835049028"},"_hasShrinkwrap":false},"0.0.7":{"name":"effector-forms","version":"0.0.7","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-forms.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"1eeb556890fdae8fff0e2c6ca2fbf4debc9669ab","_id":"effector-forms@0.0.7","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-QN+ttjINciHzrlKTRnd2HQYymGoJL/WRRPgA5YbgFrPB+TdMqO9U30vjuaY4FHHY5n3xTJbWEyPabmCDIAr4tw==","shasum":"030c993c4da9d352a231acfa6c576b86bd7e4548","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.7.tgz","fileCount":20,"unpackedSize":123437,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfRA2lCRA9TVsSAnZWagAAoG0P/3u8URevXtBxvXYqX2q0\nhgr46Ank8JaBwwQDtz4pB9u0zkwytgrfQr3CiKUmZ72hUDEG8wwpY6yt81eV\nHaAiy1Qcwid/9NUsqyGRcnJuiZaY7UewyDQBxbFQ6D8O0JRmf4uY6wFW1IWH\nIhd9ZebQ3B7xjsPFApa9rGeepNfpIH8426p8Hm+eDXvnfrKV39oBe06Z1U4k\nNXBIjxCPVjbhvUXQ9omp5CYPM0ZcX5cm8zJKBqlVlZCMTlVo84aaZDZv1tmJ\nRZEn/+FZ5hTBrAS80vb4+UUWqE0LHgpeWW1zW0dglB+/KPAfsvQ3TLx18MiH\nZPrD0PSCPoeH9yRCTFH05Fn2U+344YyLID3NBt3A/GQ+vK3aUW3HhSaSSBex\nnwsJSvlJn29Qmj2/UJheofLNqMBLFt3hLI187Imz9Gpp7ZYBRbR56vepfA5t\nT8qVjjCezMJ8HcigvQatdwdcgg1aVrSMfQfjQOjl9pYtRgkXNnKVaYbboJ7I\nCvlq/hTlQyGQIr6FZNKws12h+VxGSpfhhUozW6netaj/UTec/1JckL+mZFZh\nK1z1weJ3q6c9jBMjh4sSx44VIJccu55ueorpvdWGmdl/M+OXDS1hmNZ125Xn\neqtqimxUT6oyfEgPiawQBQWXq/xqSxQhZiAZZQWdHiDgcDSaAPE0iFwr9SGo\nwHHr\r\n=3cHX\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCwzG7DZTcAkSK4DGNT27Aft6Ss4586ciQ4g7fKY2BIhAIgWstSbWQmRS2iqfx9xOUXM8qJwcHucqZrmTSDeOpezDQ="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.7_1598295460629_0.495171528641825"},"_hasShrinkwrap":false},"0.0.8":{"name":"effector-forms","version":"0.0.8","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-forms.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"147bd2eacbf431a23891ebd3140198c52b2ca781","_id":"effector-forms@0.0.8","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-VPdhrHpK67RrqakUEwXPfBJVa31JBGgJmS/LQZ3QRzTsvzNySq/3W6PHzv9lvb2rQM2960YM9WBoX1niKQ+y9Q==","shasum":"03f8763f373517626caa58ea47eed00eae51f08c","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.8.tgz","fileCount":20,"unpackedSize":123437,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSfBeCRA9TVsSAnZWagAAR5kP/jYzZeB2x3+rZvpZFwM9\nrXc8VZzgUu9JVS4IQToLn6w1zzutvVG7+JxFQX3NoEK+TFJA+8FRAHpoh6HN\nVqQTEeRj3wgr9TZmGmkxmrz9ocK5GaS1RykOanWMLyFgZkZq86x/54dknoUy\nERB7j2Afow8KAhs/2FuOXNzJTBfYf9cthnTCghK37uKEXCcHOmeYp2D6HnTT\n5OQf3zU2syBSZydzMbKBp1fwayGSqpS/5l/HD/hZ81vPKxkTdXJUWVr0Pmev\n9cG7NeMmSWT5gAaVmPEdJbr0n4HTuyH4Csqz0FXIpot+1sDXenCxWjFLThdG\n1vwrt7yPPyVjVVHzJ0KSse3u1pPsSLdCDTA/7+LKqpySOJVBZfSacXGYmCGX\nnJjaX0P5O86d3eNQ/MD9QZJXbuth4rcwi2YTsC1f1qB4pCkqaLkShpwnsYzR\nZQ83lbKm6eG/0yQtswt68fr2UvgdXpzQdr762pPa/mCScaBgRx0Y3hFn6zlD\nUf2qMj0w9ogfS3UtNjKfrSMkh+1q3m80vH09CjJ26L+/wpDgNFIyaOsSDkKY\n4lRoinPa7hbxdr/TvIR1fAxj5mns7UAkKWTx/Zvf2bFO8BNfeCjT71INa/BM\n8IaK3wPKKxFeTuzgjv16zkg2Rz7g7mpv2SV9KVYSzIBTErsq9j7D2nKFy6mw\ns4Hj\r\n=RWiM\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHubV15hYFNdaCfJikJb7354dt6+q1diJihfBt2ouUwoAiAPZrmG6PBCBxSTSN77m+hiIZekzoQmFm+HF7hYBgpgKw=="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.8_1598681181680_0.020276533192317148"},"_hasShrinkwrap":false},"0.0.9":{"name":"effector-forms","version":"0.0.9","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-forms.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"247f6996767a7546145f1566e5b5690c594769d9","_id":"effector-forms@0.0.9","_nodeVersion":"12.18.2","_npmVersion":"6.14.5","dist":{"integrity":"sha512-6YWuhkrAUmFpeN5ZmNzpITw84vaOcJBktnog7703rEtqwVQcNnPxMliDOkNtLLKmL9n+66dxXSVmboedIK5rWA==","shasum":"a6ab6d6f4a5810088c79876d422d20075ef98057","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.9.tgz","fileCount":20,"unpackedSize":134965,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSfMXCRA9TVsSAnZWagAAqzIP/2OWavCJyC3cLnhblUfi\nGX9XoVCAuJxS/fVlG9Ax5Nd04afDyHgDwlU31jb1X5KCbLiL87BfIzobBOK4\nHAMhynIN/f1duB5oWv5t0gXt4Aw0+QfDwjPjS1iLtdFnPZE9DmISykl4/6Ht\ng1L3J1n8BrViI5axH3zzgPmv2z7CZdYzc1/Duau9D33WaUWrPjO5/UjVUDu0\nmVIw8QD+jEhH/WVsF2loUdeAvRR/MU/RBovRqbywqXfGmwWlLpPlGzyeFhX8\nVOBLRBIG9WEkK75hBDqwqZuhwyYoSy6eZU3P1NaHc7pqmTBSfygww66teim1\njVpFt9QNNtpvMS8ukFeW6Ph9amYLfA2B86nkI3hLeV9/5V/8v6jQ+IE6963B\nuW/0qXE4nwJ9nv2pB15ryNS9lFxOwVSK8ZXV6kk2Tp2jkY22ubRqkS0mixN7\n7fF1xLvFkHy35HQ20gSFhRMuPzcbEAbtLQgafO7lambGR82SP5t2GSe/GN8H\nhTot/Apwy07KPLf+6ldipngJKYYVawRrkRXR23i/0LDVcNpn8O2+rvmiHE29\n2tP5Cdn7E2cUfBV6jyl90vsd4UvxC1fcFZOBhIDzM8MTvKKLmTc431YF4poe\n1/td6kfnvbtJfY/Urp29oo3hEm6JLgBo/lJlu8oSeCZvUhTSLb+5f3pPCveq\nTw4y\r\n=Gb+A\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDklxLR5F3jl84YC/qMfe3qr6yTUjQv/GHbk6tCBkEcGAIhAI4DvFqdNZ8NVn7L9PvmWeC8lu+skvT+fto2cw996JYC"}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.9_1598681879316_0.6803845580014203"},"_hasShrinkwrap":false},"0.0.10":{"name":"effector-forms","version":"0.0.10","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-forms.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","jest":"^26.2.2","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^5.3.0","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2"},"typings":"./dist","dependencies":{},"gitHead":"c3ddd6ba42b9d479a71f5b240bf8e1f9f3701115","_id":"effector-forms@0.0.10","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-gy+SDuEkwpCqL17ifFEEopFiCD31XaQkfNk7aZob84i9/2NJDwrvc7y8sWdbd6/Ka8Pf+cR//SsII+aNNsAagQ==","shasum":"11a89adc6be502d06e077f653828346536c9b418","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.10.tgz","fileCount":20,"unpackedSize":136372,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTPmVCRA9TVsSAnZWagAAZ9gQAKNqZ8Pt8KyrHdd/XwGn\nB+AX73l+fR62v7xwZxROOUDRzvtxk6BVhXqVJt7F/vnNSlvv1OBagONp0dWs\nIMo1qFvRcHFCjqLqWTt4Rtgvsc7mN0TcdnqwZRI4GrH4vS7JRiwfvMLkNehV\n9aLVqlBli07+A4FDaVy/+iUMsra2ZdzLLpDl5qnqmyZ1r+fkzMRQ2Wj231iv\n/mKoZyvaFwvUgDqrIwoEBzjtbNRzRagwkWoJ0MGu8WbK/2RC1LXu1tGjp/LP\nXSV7XBAbmDFe6NPf/i7OkbRG4Eg439OERH0etAgDT9XlxGpuxH2EbgSVtHwg\ncNWVy+ofRL2ixAJnDQPMuMugC3WdEGKNT4TJsSm6r1K2QROtpGZGALYq1V2/\nHFsNUlTmzfIEi6JPCK5U5TplrBi6znKG3BMb2n+rZdunDUyT+IMCi3VFBRYz\nBng/Gjtx8o7fDMc2EBS4zoK1qhBb2W+LIrqdZ6icc52biefOMHSWbKVNus1z\n8vbAXnVmbaUr0RCZqHRrrfiG5oJu3FsHJt0Knavf/B2+6jRfUoZOJHJn2ZJZ\ntpsgeqlJ3usZpoMfov90FVunbblUwNGtyb2xp1E9fWypTrn56D0PEkIpWoml\nIVvlq+GGypy6lXk3XAM0q8S0cwJnVGJkpdrAF3/62VFOzaFd0SVf3+6ENoBg\nSiHk\r\n=31VN\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFhvuLwhwIm1vso7AJMmjyU0GI8p+3TuaBpisRLM1A7XAiEAlvube/jiK30yBydK1G2Gy17VfzNHwFbmXK+7wmAveD0="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.10_1598880148550_0.2813450373912485"},"_hasShrinkwrap":false},"0.0.11":{"name":"effector-forms","version":"0.0.11","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"994ed67b355634c9cba2385723ce04515b119b5b","_id":"effector-forms@0.0.11","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-BBbp7ugGqgnftbukfDFaCCeXxXBxhqZWiz8pbk1UAn1NlmFFuGKu4oS6ZZb4ZasTzn87FOGeCflMWm0sh2Zgkg==","shasum":"507f0f46f0533747faed7721645b6d7f94f26e16","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.11.tgz","fileCount":21,"unpackedSize":152276,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfaMqKCRA9TVsSAnZWagAAnN0P/04QUiAeM87saTKTgngk\nuGkICF0iWYYK3RX0b2n0QiYW3XVlfGzgElYuQ8LjqjToXRufyNo6BkV32aYm\n9hkXdAOzm7NgFkAG7lwezwkfuPlI7ygA6Q6To8gMbxlGkQ6qzjTVLqDFy/nO\nmrLlaQmlRMfjWj4I49T9iOK9MzrnkB7KJw9Lj+HQc30No6FvR2zWMbMtHs0d\nJxJZ4IZolZoKWltvlGivkr5fYRW0DOnHSqC0ZAtY0u1qG9BCt3L1T/vjQa9c\nG/LYSNQ+ARhdLMdatfkth+g8RaBoIyGWLSaKj7efMegkCFMOGHY2Pmpk7LL2\nQteMOSPqxYSlfZNSVq6nFwmcmf8CrPgN8eMiejFHzNgrv1JumZwUbRnmWPkc\n/1iK79+H28oHPXSCf62XSd59SkXLLkxl/bttQ484zx3eDrZJ+q57PixjDSWz\noqIc7GBnkhfgof4gtvPS9mUovmCxfSkv8xyroGiV7O4jPZR/o1rfTPWDS5k2\ne7KLzREf3yaJPUP8Y486viGwcFtDDje9ey0GwHp5wWFL+xbHClSAhC0167J/\nSQ0nmqB9yRFr2qP5va9/fYAERUZFn4sRj7Jrba4iH1HU8LgwncetVsCapPeh\nweodOaDmdc/DuJWDi8BDkcF4hpYeaBv5ztatTLWgoOHWi7e5niOKPmdMNZmI\nu5Nn\r\n=XKwO\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHuI2CJblR7IAwzPS4OTN4BMrBASrMZJ48rjqqkTkSeLAiAaae5RnNe4FDaNW+KJKribZY1W0dBu0Owb/9twT9dkqg=="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.11_1600703113522_0.44917510757489576"},"_hasShrinkwrap":false},"0.0.12":{"name":"effector-forms","version":"0.0.12","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/effector-forms.es.js","module":"./dist/effector-forms.es.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/effector-forms.es.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\"","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"ba20df7b96f56cd9c7861f3a850319031c71f0e3","_id":"effector-forms@0.0.12","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-fTeiyWMVQAutKk6D/XAj23JA4zVMQtS3SHfMSrMxkHfigA/m3IkXpO4Y3lOpxeK8OimJnP74qkrCOsz3ZtucSw==","shasum":"d407dd2b084622e388f036ec6541f9e2d2cf399b","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.12.tgz","fileCount":21,"unpackedSize":152336,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfaM2wCRA9TVsSAnZWagAAcOMP/R9ZWDWvFBBfK2a/baFP\ns51nBMg9Jj64ZTnA3hKYumen5RuuP4zem1TfQCA7UfvQh7CWsV4agWb5u+RY\n1oWcwMbbk0h9XlAgEKf9BPx3vAqOW9/1aLnCtz01SGLp5nmfMgQgpF/I7HNI\n5DmGAiR7xAJhR3fHv0zYiDnrcdh383IuLbPA5BsTnge149sycsQ19Gyb/x4S\n6SqIRFyBuk2pq7OOkrFK9mwnBAGbIN9fcnHILg0ZN3nmLcqLeOapOYDUhLXB\n47m+nNjpKaHKP3AGQQU0lArKSiFupVueaDrD4CL4Wwphptn3SK0HTijqsZcs\nkS106Rqvas9CBElwB0tbMsUG+vzvDkbHJ+uuAYmH6QnQ4iZk3flZBbINbPja\nfUxag2aoggQypoBwmDibykSQi0gilD6tR9Q7bFjtruRbqTjFW/IXL0B4oRqq\n6k5+zKB4mtVBRhVB1HiAZPz4WrWcPJkTKE0m0hJtg+9gDnPIWiNLb9t8pC0P\nzJauxUmARwaD8LmRxCHK7x4AyZ8i5wAmW9riFoQd2jrgJ0xExOxZvXBSfKS+\nrXKIuk78vVri2ovdpuFt/defgLMBbp5o4/LkdDGDWkUNFwuBpJjJmdusvo1f\n+jet39dGBrbrv09TxxVnDzFyFm+BhDt3g0Iun48jnqcqG54eicEkQNJH96iF\nz6uq\r\n=HNJ6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDEZ4nskjDVXBdYNsjPQJFoipru/fH4VJT7imEXWPbO0AiEA/fZ+q9LPEby1d4vpakmRKm5772VNhxRYXcI1VcMyf0o="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.12_1600703919685_0.18514475925233365"},"_hasShrinkwrap":false},"0.0.13":{"name":"effector-forms","version":"0.0.13","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"0701041375759398c325ff0139b88f63151c9359","_id":"effector-forms@0.0.13","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-4rIjTnzDRu4VKSIr2rQGs1wRQF1wrYucKq3RzQkMMchWqU8fbEM346sibslgd0O8qZc9HdxFbCw2WG/9Q1gjcw==","shasum":"2a01ac341b28153b03f8a042ecdbf833c7445dce","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.13.tgz","fileCount":21,"unpackedSize":154632,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfafCDCRA9TVsSAnZWagAAl50P/2m34Kf9AYzxz2TF9yva\npSBhob/3xQNHB/WVSc3ycuWtDS6Ra5Z899o9gFsvd8P4t6ZbtnS3mPEASFDi\nfD5RFOjyyXZ4sahKa+SZFq2kPT2+SwQ4YaHGWA6nhWr8V8Wh6l3rFT9W3xys\nxYypxAeJ6jpIr6iMnf5Kkl7VlSxjJVbAwQKxYdV4zA0cb5zr0AndS4ooPxB0\n+shFIEf99zm0fWBs/vizg+2ELurKVOuV1jlFsZLf8+vdAUWIRHtlOQe3YlR1\nYekwH7yRGTK8O9aRyVGi7eaX9OlqxeVv4nvoTeACdlAi4tvRvNpm6HHlw4kN\n0d9tr0hczIN3kBjlQiQfe7+6XSd7ZX9FyBXMefLLQ0VP0aHch5LSuubnBbv+\nern33Uu6xxdt+2aCMvhBnthMMqWK+EPwndTJY2w8CBhEJibqZA2uNL/mmjqj\nl65VU7aojntgxu3OyI8ngYjO2XqOhM6nl2cMVh8GJSlJr8C+tmwiMIEEnmvc\nTV7S+Skts61uAzbvaZnvKtBmgE6hEVbDL/NYjeGrBL8x59dnXcHOXdHk7edK\ndqymhVsDJ2pKeEF3i7FnyIsdFmbWmA5JDFxx64EKebfS5CbZrgVt8X/lThmo\n6vKfp2LLXfUl92xaQt0/y+jF5b+mcCsEvDPn2FZ9PyDQxdwTzZMsNrU8BkNW\nCby+\r\n=gnts\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC87tdpkAsMZgtE1Li1wl4/LBaDXV+FLHMKLvWtgfTkWwIgDkJxnbIuEvQsBaUGKHGwmr9RTfed1RqgQdXqFE+J4Xw="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.13_1600778370757_0.6116603805603904"},"_hasShrinkwrap":false},"0.0.14":{"name":"effector-forms","version":"0.0.14","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"1a8d1f5f3b9b2ccc445e62a64e96f685ab333113","_id":"effector-forms@0.0.14","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-AMbKNe3N0wsQqPY2jMQ+cAc33edePgKb2ayaBnwjusYEKBXag1VeGEzialptxvw5Yrv6v5noFVRaO19SVvt2Yg==","shasum":"73a7d080f210aa3352d1fab7a637f4fbbbef3aca","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.14.tgz","fileCount":21,"unpackedSize":158875,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcdJZCRA9TVsSAnZWagAAlUkP/iYaQtN84Xow62cV4da/\n6zj18EWx5l4ehpiPT0EjBKtIYS+e5g1Aggx5VU3SSDcZSl+YSAOBJI6w0Rap\n1xa0GoGkHXhCZbDf2y3ArQVOrKafdDUtfWAnlkaMjn39Rs4dyvc2l5BzwcDL\nvz82b2irJsiMkW2sf2sRbpJ4bB4LZj/gVIky38o73deuZq262cPfuzJVHeS4\nhzwMZwmeLtfe0YUlHYAd3+5OicenVIu71kjS+NsLuw/act3g9eqa4+7GQztb\nh0EunyEp9kCW5RXSozMJI4BscMCJPfukXX2So31xt0Y/dSgbOJI62AGvu6Mz\nWOctjFtRWgQRnS3RaMKAB6DmfU1FmaLS+6RzjSpQtAsVVZsLXP6jVvO/k8bD\nhsGXKPtrGI0OiGHMISyAII55tWFFCn1mZDC4LpnR7sCoUUP0cgZQNIwXozEJ\nPDMdVJjupAREQwQP8dDt3R4siOQ8Mg/LlX+vQqq+jg2aJYuCxjzJ89f5mcKr\nL76V7YbGxKaqLoEIm+g3vR4T6KojwU21GX40IA8DX7FgCA9+yoEPxceBYKpc\nB7r/8VtPzT/4Ae/1L0iijJEty/FP5BfrZywiZok3PiWJxIuVqYzf399PjiOo\nyCZ2BmB4lB1OJbvi7yLrrmOgsvn0IHuujZqMzOb2EGgAlBPEHqWcWa/9ZwNo\ntx0T\r\n=H2aZ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDNV0SKD6is4VU3mXyA3TQ9Q4rP8m9OBH/3/U83cWOmLQIhAOpMy/EwQ3cf2X4qrhWqsjJUbrgG4kFi6vEL4m1EvUF0"}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.14_1601294937337_0.2142932317339128"},"_hasShrinkwrap":false},"0.0.15":{"name":"effector-forms","version":"0.0.15","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"93396448192df245f64f514b9330170aae45a290","_id":"effector-forms@0.0.15","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-pZ3rFPW7K7FGx7x9Uhuo4KuAa3Ntq/YNWGt1j9Fh8fTxxMOfmMYGWORGm3WCDxhfjMT+OA838sCwf/MKgMzM2A==","shasum":"5761f9df66bad1634db346654e072d59b9ae920f","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.15.tgz","fileCount":21,"unpackedSize":162628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfftK6CRA9TVsSAnZWagAA+F0P/228YgxhyREU1roKnv+H\nscmpz8RP6uIT8EktT+UsZoIVUQj3ESMCW/EOndvsMYTHF1CWVdpUduuuwsnb\noeYfkrXN9SxMP0tsrCIFBigyJV8Ru15j62ZYDVMal+Nht7dH18LvKcaue85E\n1X/4v7EUs7sXwtrUbnYZ23fHIwmSA6BNoMW0jA50mL4HNgM6TKn8l8vCFx/8\nALK0x+KFR5GdfmWnNwhwnd4yptDhCcyFb42hbioUlH6b52a9obD7Qon2LAJc\nnYtHraXAXssgrRUl5hchOSAzenzAFvPSbMzMPmGWrDhUPykJsiutP8ipMLAr\nGqdxEWY1Kn0b8gLRgzWlBxrPFzBAac11XRpUmW+0d4ZY/VUKCLkpJ+FG02N+\n8MzgY97IBiph4wEp7Uy+Qo7WY/IO5uTnpFyJgy26kTpYkdNUnWZULN7B8Zka\n/2cFNVCcZZM7IEwJQFpTHz1MOViTCCdocW8p+Edn7NW91Gp3qaD6bd3RTmD1\nWnLAnxxWWCMBah9rp0vTr/Micx+mRQ/PsWp1BnfwdYHor7oaZLPi2WB044IH\nZiKMZyTAg5iiufw7DqpbBGdewch8J8JDHLyvI5rPLSF7WTIkr82jLnAo/YkX\nOkgM5jbBUTUgqOE0efwz36nuIKyPXjkm6c7Yj55xVttZtN8mx3+SdgmgO0Oj\nARKc\r\n=8W7I\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIQD8QREfC8/TPG7NBMDwEscJenuqqAFYvV6MroCR2Or7fwIfOuts3PL66L1nrXfaaF2KkgpHNOxKgeWqiBDOk6M68Q=="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.15_1602147001965_0.13784890670271155"},"_hasShrinkwrap":false},"0.0.16":{"name":"effector-forms","version":"0.0.16","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"5a8d39cc79efe36eac3c28805374c5996470656f","_id":"effector-forms@0.0.16","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-tBwayon8lS7bZIHbmx8uVbuL23OenzdrVmUqMe5aTSpDecIHCkwv2JYSLpcgGpgyXF8/bn2HbXA2wXIZ+i4u5A==","shasum":"c8ee2d2cd8225d26b1512689f2e5141aef5b9c18","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.16.tgz","fileCount":21,"unpackedSize":163987,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfftqZCRA9TVsSAnZWagAA724P+wffX+Eisk8C9WVwRDls\nySpnfiQvKkOEMXxPVxEemhcstAir4PPzXp7koRwYOmrXUoUC3VO0kTp4TjzE\nmAWM1u+o5ZgliTiMmYNDpZHpr4oUsVdyvy4w2Atn0OhnGWXVkpb68PMndGKd\nHGcrFVCcaBFeSRyhL+ZMWCgpNxpHnmLS77T/pOEhhBps4aqYwZFI3MeGg/Ul\nOkjTvQV6zIhdjOErFr2EOpXdRrZmYgqhebfvjeEX/wT3Ud/gGHR3ZHvkCGeI\nWTS5BdozjYB6PTeTyHFAi1KVqCUADpCYJkZam0H04z+FW92fZP/LAgc5TeWc\n/aQIbdpJs6P6L2t0tpVLbxoWPmteWqj3tHfaMA3Ltcda8hZYW7X2Q4ixazW2\nH0OBtCPfzVvc7Vzl7VmAvDJ8mmIl2eJMXSg4lPZJluq+5Bxrrif+A8swL4DW\n2TU3QI9B0ydbLTds5bzQztiXIr842P6UxURxTF2ptuaJTPwqxUy8j4EFGdFG\nP7wRp3upZFVBhgTGHE7tMtTyMsWNbqPqkk2UYAvaMPmSyyO6GUje/lOKMUNb\n1KkeufkAyLrp/4Gk+pbn/19P8jbAIfG/LsvmBot5Cix2J954JlwQ32ADmYHb\n/pHj9OCyhzwrcOA47SUJdt0+vmrwKCjlDLUIhZ1elBh5w03OoFzTgLLmwua7\n+bXP\r\n=0XT+\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFpCn9hhw17a9/IpYKRB3TcVh+m+ATUac4LsOrPL/WqZAiEA/lGIxvPJnXIdrs1r8JGsQu271gwfJYAILW7p3ONU6LE="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.16_1602149017181_0.03708811405039514"},"_hasShrinkwrap":false},"0.0.17":{"name":"effector-forms","version":"0.0.17","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"53f562bdec24674ba0f4a8879dd70349cfbba8cc","_id":"effector-forms@0.0.17","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-QUMxwDIhTowZbglrVeoBQ/MLw1DQKHE4PGI8ykk1KrnhfXzVEOGcdR+H55+IR5gnRqhj3gVWqyG6LIBlvgS3MA==","shasum":"45bd630ab92678f2b777fdeea155a92257d1e903","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.17.tgz","fileCount":21,"unpackedSize":168508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffuTvCRA9TVsSAnZWagAAWKcP/RBv2y5yA6Tox27HWq0h\nyVPtI9FVjNd4bMghBYtWkK6zffte/lsqJXEWF424bQYypyPVfVYuVHw3t+jL\n8McG0fN7MeB94BKDGTAtM/GQFmDuDi62Bed0UR/xSM8b0YQYrcJkf87W8WZv\n7veEzPkwEa7iyAjAAeIe7pfg0mIFASsqWf3xPuE6K3K6CLSIhYoXrOOsoksc\n7GbAYMjW/s/8zOz1u0ozl7p/H3zM8r+UAxvL7CMxmLNZKCyZfWeySGMgRoMo\n0zyEUJKvvXn2XlJdexoDPIwYuoVW3HljI/SuG8RByXt70JnnDbefpI5LsxgV\nnVXsKPHwLIdVP6ZVVWdOLwMAwgWZ/JSiYTys11MBbau4FUYBquNUro9gPkuG\nEpVokPI6dNq0RLp/Vi0XlxsyHao4pBgGoMH8blFnXTDNafow15rjgV2DKkWp\nrLHxkbb7SvaNvnA6wJFhBRi7XHXSEar5ZyPTy+OoyAGnhOpNBn7DCuZDxs/L\nhfLvtejTfz/MCYsZcGrOFrejPuNDkHDsq5D6R9L11WrXrzX9W3b6npl2P5Br\nfxDd9E9HpcqtCSr9rALEQW77bkyp8TanT3EEEgtf+7LbwsUkqorEKnfnF81c\nIWw1FPY36QjCMbjV3Ovg6rwwl9Nd+KooKjnDM9dw8st0wwVI/0gsjtIeTpy5\nWGM+\r\n=M60J\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCv2y+D2WlOuMhEIcbz/WPPAOrFaQN10ObwFMPKIU3eDwIhANAoS+L/C6gNNcQe5LuFqIdLVaFvjZaYJ6OM9J6dg5h0"}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.17_1602151662876_0.8268918259997851"},"_hasShrinkwrap":false},"0.0.18":{"name":"effector-forms","version":"0.0.18","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"46163e8b60638ca09b98431e95cd0aae495ab6cf","_id":"effector-forms@0.0.18","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-60VBBqoU2BSsc9YZb7UC3SK2f3EkLOXm+uu4pOwGufQ7vjWX+kSKfRK5eQrtqefJBMjpvIGmathVUhNjRUe4QA==","shasum":"fc3035cf33ed9022c667e0bfc2fb053c341082f3","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.18.tgz","fileCount":21,"unpackedSize":168809,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffuvICRA9TVsSAnZWagAAJJgP/1BqZE4+oTS1bAwfOhDV\neIyHValMHkicGhNp+K+QFrFzVPwmvaK12+2J4sADC3I1Gr7l3xaDzqYpXQ5K\n1S6jB+VMX9D7a0u+I//e4coa7jD1MgfsMcTJmizPfvVScK+Ge7qi1DAdfBwX\nLrXDm418F2k0iZLs535CQwBHofFu8+DuOz4ltyVw7xKFajyJtzKo15b0vHwJ\nQq80jGDfGWtLfVsYfDag0AoXF6jqC9xcrYtT0N1ljuxuyCh90S2d7I0wIyk9\nVtoWzioyfsxpSIwPTB+GuA3fv55tzMMBMI8vrAHri4JQ0BDLJrkcZgR1O0I4\nL7z7SW8xaV0N3H5bDOh4yjs1e3+DisAYyz1nNdF0yXW2xcW8bBV8F9pw8WT5\nruxw5YMivcvFroKJkimmEaORkLekTscDr7+H9DaVvxq1PG77KzZIJZdmbatr\n8l8qI8piNJz7GmZ3wDsm8GEFhA7PqgcWr6kBH3tEHRbQWO/0XloYBXTm7ams\nrxBCQlocATb8AKpJg1aX/fpRGyAwcdBE4RoJqlFuTrBVkENG54CQAD9S3cSb\nLPdnRhdPGnm8IKgUi9VIX/a6vWI1tgHuvL9l9BZbNoKIBVeccfbyscgjtJmY\npW3BUSSUSgXsE8njQweAj3uhjvf0G7lsx5P3EVl3olML849t17sT3tukH98G\n4PPc\r\n=LcMu\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCueqmCnkrkXKBgcm35hiQIhVlaRRcDxMnDIYSiE6uupQIgBEHMJ/EhuvWXEvzhcUWOc7ksTgiIUQ7lK60jsIIldlo="}]},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.18_1602153415887_0.332133263876758"},"_hasShrinkwrap":false},"0.0.19":{"name":"effector-forms","version":"0.0.19","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"5daa6f57ca436911d1077edf6d07d19ce0174a2f","_id":"effector-forms@0.0.19","_nodeVersion":"12.18.2","_npmVersion":"6.14.8","dist":{"integrity":"sha512-+aBWnjpMn/cqF0ceo4m+fDjSWR9GhZ6iRsv8waX0w4d61ofsX60WpzOKLHGzeJQLEE3wYCuY/QuQhomqc2FZww==","shasum":"d3e1786aebf023923db6362dd61d4d3ea7f9e3d6","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.19.tgz","fileCount":21,"unpackedSize":172292,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/FrBCRA9TVsSAnZWagAAifQP/A7A3gIRBtYuau1q5Tgv\nieHFv41WQZcHnznTUwU2o8aHUwnoKLhQS8aIzUFo1T3Jofla12cou/iV9iIA\n6CeU2MhXsdptXC8Uq+BE/m24G9gnaEvIqCW1yw9LmQzIYclu/2GBvYKXrbpS\niXOLGTdtZvo+EKl6Glb2l1U4IMGbTFoIThukC3f/cfzeVeUnIVTArb69bEjc\nYdvIoSb+OG4ViCVZFEq7vA6ME8w3i/6zswO2tc2tYepwVEUW+1RNDBnKpTfL\nd9XhHlJjojLwDSjCeC78oxzppMuU1rwep4MzAiyNmiBIfoEGUMBBqQAhWUBt\nuApdVIws/oz9Es/OlvaNaDVWH4Sic4hegwaujdbWb/HXtqgj0a9VtVbaRIfA\nmMVuvD024rEGtaPMeYXefuEZT5Nzn0GetpMDDTtRjh6XLaZbH/9Srv25DyNB\nWFvAJdor3VscQYxvruzU8JxpaBUf1S4kx6+z5HrWToxLMicBTKkgBgdRu/BS\npe1QiBtKEwlKhkCxih2lku9tfxYMqXQqYWhHLYp4Rci+50+yfeDlfM4bWtm5\n9DN5lwqTKVkYp8ZbjYMYFDSh57rZ/D61F8VeHvx6NbFElQ0KunZ7Umpb0/q2\nmnDjITxSKjKZb8KH0tkedflv/xLmvAROcyRtjmT3xHyXVZelDOnC/x5BfbGV\neCQp\r\n=ZB1M\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEnE2N2GgTx6+kVPGZLZSLo63f28NAYqKRSeHqOpPIpiAiBW3Wi/MBBR/gd6MkAV/clkVcr7sZwTZEaTAhcy1cW3/g=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.19_1610373825283_0.567820982385614"},"_hasShrinkwrap":false},"0.0.20":{"name":"effector-forms","version":"0.0.20","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.2.1","effector-react":"^21.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^3.9.2","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"dcd45229c627b53f0fc2a2c9af5258337c99b33d","_id":"effector-forms@0.0.20","_nodeVersion":"12.13.1","_npmVersion":"6.13.1","dist":{"integrity":"sha512-ctn6tRFM2Ab3yA+aUtBug18c7RyEa4Be2rocZjQb7bHqybOsU/z9YQVzQcDQ0THxQf0f1H41hNTZqVS+p1xO4w==","shasum":"feb471ecbecf80a1506a15b06a000d202d7f3f1c","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.20.tgz","fileCount":21,"unpackedSize":174624,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEqWBCRA9TVsSAnZWagAAqO4P/1+0exDNGK8a6dXB1DV0\ntFDYmabPhoOQX4KgbU+L+Of416tVaJooFrFPVl2dnLm3BwtC5+/Kq70C8buf\nj859xxvzYoKeSqbRyvuCarc7wdnP+0VUiUgeh0xMl+xAnD1NJ87Yccu8uQR2\nZ8DU3vuJJZKAJ3Gtr2D9uij5fUxoSNVpHLquHx6mM6ebqfjnSZOdMTkHOs9Z\n6ezmiJZy5ga0pJtIUO+3+HUIqdDV5BkLVPl1i75ggvxnOUMMBWk5OLdDvfzx\nBl6i86qo2TKCuUq1PZlvKlpHuZohRA4y7oXPJ4IXN/tNKOcXtU1VfUBlsv+w\nqAG7PsgP9w8hpRTbxHzf+fBkNXYqgL30sGELqjOsp3S/e9fh4O9UqKfDUTQL\n3xQutN5qJJ363xWyMEcyZM2lfpic3MJiDAZugsgshYlSYAZvB6IIvMrxyUFB\n1e0asdiYceEQRa/MY3E+YOk/x5bOt2StGqhJyb2z1/7kpbG3uyTw189uHdpm\nQ7AKznwtQFXjb1ZidwHKkvx5BGv9wAtcw7X374S+2uyXmafVPako4l8pdz9/\nAngjs2QtoyTC2qTtatHtOVD3Ho/XXTU5/mGrLO2kSLUring7NWv/KlqPzoW6\nBVkWpw1lZBwlYqNURL0I+9LHjOVw4PQSBjNKn+QiWo6RcqJFU/Fk1bO5TcNp\nqU1L\r\n=AEWz\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCWJyzbAtHfgtcZH6CjJZ6sADNl8xLXwK1ybRGY/kBEggIhAIxkNaf5osSME1SPza1nsH433xS4GdRdk9LjcUpRPFXE"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.20_1611834752577_0.26571858301712914"},"_hasShrinkwrap":false},"0.0.21":{"name":"effector-forms","version":"0.0.21","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.11","effector-react":"^21.3.1","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/react-dom":"^17.0.3","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^4.1.3","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"13aba00e4168ca3ff912ab785bb098296a055e7e","_id":"effector-forms@0.0.21","_nodeVersion":"12.13.1","_npmVersion":"6.14.11","dist":{"integrity":"sha512-+v/jnI0fzzugWMNwHWDhPAVTpSbRjBEd26j9uQWnfBVJGrMDpUBkKX3exDhNw2Oa/ibt6p0l43e5FxnGeS9N+Q==","shasum":"80edfbc3468d9b4dbd0d179e895dca8290071f61","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.21.tgz","fileCount":21,"unpackedSize":178701,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbZDeCRA9TVsSAnZWagAAmz8P/Aio6B3t7JfAO3u5YUa1\nlrdOnFfoFoNrIXCTMGsJKE+1rCCpWNbHWqhv99RrIAU1w491+5RU9Y8uYSKO\nNK75UBAnzDVwTNM2fOvReMG4ZoUyfzoOREQq+UDRhpqZs35CDrnNB5uBKI4t\n1xZMIDtSpjIDVhg4qikT4VyCdUwltJmr1gtS5/YmTFJrXXSY4avINz6EDwvU\ngrAJ3QbBc/7AgFcMOi138z/gfSP497EpYl3Nf4EeRXSvzOKzjt/dwL1LBWGT\neX1c62IxrMaeVVvBLrrqn8k8bH2zbCqaPUL2tEKatDBeAyiruayVLqtzvxVm\n5ILbtZv4W31nj4MByUXSBvCYn/tqhM3Tx4fTZU/eDuI0XH6kdWqzMJZLcZCj\nA/T3HNxj9SYYLRqBo7KTENhcHsaUxAuD2vmY9kChE8A2A39fEIVoYwmmSUrh\n3cqxTVOM+1AFEgvuOF1IFSNx3K/J9DA/VvuqGDfZCNoXQgqbsI6NXE8ARRSQ\nHrgC9zptw8WIcdEOKPIJNt0oCCWQfuOei/9pYiXxgR2vGJl6gghbHw8XLH5u\nts7YamGrOHn+CsgTOug8FfTKvlFgVIPqGSIbPU1GHAMdEoRtu4lV7zM2m/hG\nUs0MmLIhamRwI7aa+oEt6rzHfW3pDzxTQ552OCmpQNb4H8Sma3VB6FPJBHO/\nkzJv\r\n=lUnW\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBrazwZzFzND1du+kR8koZwYkFKxJm9l8rHQ9gbOJEP3AiAfuwulSl4SUvXCE/L23qjxYp48xpH6fbcDyWutAwFxtg=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.21_1617793246333_0.09239268748610141"},"_hasShrinkwrap":false},"0.0.22":{"name":"effector-forms","version":"0.0.22","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","scripts":{"build":"rollup -c rollup.config.js","test":"jest","example":"cross-env NODE_ENV=development webpack-dev-server --config ./webpack.example.config.js","lint":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^22.1.2","effector-react":"^22.0.4","react":"^16.13.1","object-assign":"^4.1.1","react-dom":"^16.13.1","yup":"^0.29.3"},"devDependencies":{"@babel/core":"^7.11.1","@babel/plugin-proposal-optional-chaining":"^7.11.0","@babel/plugin-transform-modules-commonjs":"^7.10.4","@babel/preset-env":"^7.11.0","@babel/preset-react":"^7.10.4","@babel/preset-typescript":"^7.10.4","@testing-library/react":"^10.4.8","@types/jest":"^26.0.9","@types/react":"^16.9.46","@types/react-dom":"^17.0.3","@types/webpack-env":"^1.15.3","@types/yup":"^0.29.5","@typescript-eslint/eslint-plugin":"^2.33.0","@typescript-eslint/parser":"^2.33.0","babel-jest":"^26.2.2","babel-loader":"^8.1.0","babel-plugin-module-resolver":"^4.0.0","cross-env":"^7.0.2","eslint":"^7.0.0","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","fork-ts-checker-webpack-plugin":"^5.2.0","html-webpack-plugin":"^4.5.0","jest":"^26.2.2","react-hot-loader":"^4.12.21","react-test-renderer":"^16.13.1","rollup":"^2.10.2","rollup-plugin-babel":"^4.4.0","rollup-plugin-terser":"^7.0.2","rollup-plugin-typescript2":"^0.27.1","typescript":"^4.1.3","webpack":"^4.44.2","webpack-cli":"^3.3.12","webpack-dev-server":"^3.11.0"},"typings":"./dist","dependencies":{},"gitHead":"c1bc15752587e1f970ff1665a109ef5f8f7197ed","_id":"effector-forms@0.0.22","_nodeVersion":"14.17.5","_npmVersion":"8.1.0","dist":{"integrity":"sha512-TykyuTpLn9cr4KtkVpOqf57qwXtXnYhOL3bIQPvygVNwovh8jelRZmFSivHwcmQWzQ02401AWg0XUIPAat8Cxg==","shasum":"61908324d3064facd13d8daf14bdf7a56e0c6e6a","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.22.tgz","fileCount":21,"unpackedSize":178699,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFYgLrGQ1kJ5cKkXRcvGmJz0Ob7qdG2IOF7M/8taEVTIAiBaIq+10p2Klkz+a6sbIFSUNiqvbSWWTYLtMp9hJ460kA=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.22_1634759974977_0.8993177918111024"},"_hasShrinkwrap":false},"0.0.23-alpha.0":{"name":"effector-forms","version":"0.0.23-alpha.0","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","babel-plugin-transform-inline-environment-variables":"^0.4.3","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types/index.d.ts","gitHead":"88585c4de07f5383e72c19cbf1fa8fabc5837835","_id":"effector-forms@0.0.23-alpha.0","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-I1h0SdME8/8LV0uOpQFRwGNv5RbROmiElCJs3tpt1gc2OujjYQZOBhxZMcX2elF2yumlGd7vlGKqYNxnK67YCQ==","shasum":"7f5670623991193ef958aba6533886d4648bf9da","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.0.tgz","fileCount":15,"unpackedSize":265271,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtnFXCRA9TVsSAnZWagAASUEP/iiKEZJm+4sxqE59LgPL\njwdkgIpuW9GZjGBRZ0cvB+FNoTucL4GtkWW0RfkGlbnjthoyVb3IB/dJgshm\nmIzGro97qDK/qudRE1AoIAsIgEMzddVy489xpswlPxhD2b8wCReCYofFE9Wv\n14OJk5MKnqvKMyWDUdra9ScJKuDeiXcWXXC2uZBF6PlmrRIgMC6R56ba3gV/\nHkl51Nh/kbHgFe1ILabbYJrNknUcQF+zpUlAs50FEsuCiuaDborVvoGcNqEt\nKaiE76gX+JHChQepoiDdSZqb3w1Yums3o2uLRpEIrY/osd2G7orwffBA6Ay/\nKpFUkxWhLABnd6UgmYAOYSa5XjGFEsY+UQ2oTjbfuTICXYgTAt0dtjAbuv2K\ncTyRVcfV4hDM8E2XiiIImfIoJ7zHS6K5Vn/gAMGVKB+Wls6T5BeOTsKvH5S6\n7u9gtItYSkNhkhXYrmjqK0Bgue0fAvdqU5tpHzYrGLNvJFIlkaK4HJ0lGVkA\ns+/yFBjmTuiqrZNgHA6Jx0RvjJT7OlT47Z+SuSMVi/QHFwpxjaqUuqmz+Svq\nUP3ZS3sIcHGjPYzs0WvTwdpuenwTv+HN6Ze10A6tHGTZr8NvYHjPfhz6Gikm\nMAq0UtIyP8iflw54X118L9DvnIQZLG4XilzR0k1JQI4fr1NV6XcKBhH7Kfbj\noZr3\r\n=msCR\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHpDboYldyha6gjr032u098GBziX4Vo1JdDsuRGlsPrWAiAFghmN/PJQ2B9WXRczgm7yemOM09rJuGSOuOeOcSRxyQ=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.0_1639346519473_0.013894904043474599"},"_hasShrinkwrap":false},"0.0.23-alpha.1":{"name":"effector-forms","version":"0.0.23-alpha.1","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","babel-plugin-transform-inline-environment-variables":"^0.4.3","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"8dda5e29b6ddb149d42641f0e62c596cfecbac08","_id":"effector-forms@0.0.23-alpha.1","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-VnMNt4TsnxNMi1tX2rH1LloZgv54zrQt1/wTL+jw8y1s63DxzUNf8Fb6jpHNSESsp6eJTAXSmijAPXkDTpp5Hg==","shasum":"a01024c004e9c4ca64ab02d77fdd4706e08909a6","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.1.tgz","fileCount":16,"unpackedSize":265334,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtnfyCRA9TVsSAnZWagAAQz4QAJzLNEEVWPekbmAnvI1z\norXIYiJ02YsOCSuSyOklGXRNuW/piQhSngI6W1/AsGpMT7HP9Ac/FnRv+ZDo\n0PaN9Mpu3Zk+sN10pPuC0nIIKlkmJMio5K0slxJued4zxxL8xTgqrPiJ3UB6\nFBYrK1Ecv2braXXOl2odFnIetlnvyitWxBRVh3KpdkIvXub86nxUkDOgfaZ3\n70IN6Eiu9h3JfXJMFAGewTAOtKc9v08iD0Oyy8ILkGyAnjJVsheFWksasSbz\nx9YiWd2KF90jLSZULeHhVrA+Fyd0AI2ldtloi90rQfDhJKRw+edKHR+p0gEU\nDMQPRIDt9BVqyt3nqcMIdHKbzXA14QAhmVh6A14Hb93G/fPK6B8Y3pXG5YRf\n2muCXWwHg7lRg8v/2IE6iv9zE3X7uzKrAxhYbcZ+5/+0TCArcFrCH5WygKQ/\nolKYKo9KVWfhI4CZc7XOGUIsy1Z9mX4GxIjFiIrPoXQBuoxZYoyNskRz8cPl\nNydM086mBl7XnPAL6kulb9/LqqP786Hvvk2RSNRZZ35M7XawPDkuAjodH3sz\n8Dg3hWRsZB4N7I0nB1yPVdKHgMShH7anFFc7XChYqhbcYsCcz9s4O4yE47pW\n3AAzftJoB11iPjIxzpGmXzyoygMerd6JOQ2tAhzORXAtdXVRdVo8rc9CypWn\n+xWd\r\n=D/I5\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDQkN78zEFFH3tMAZKvYqPTg9GcEuf+YwXbGtQxnFsErQIhAJSu0o9lpeHfUysDJK+9k844FSpMpQ+yHwav51rlrgeR"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.1_1639348210793_0.42392373590293264"},"_hasShrinkwrap":false},"0.0.23-alpha.2":{"name":"effector-forms","version":"0.0.23-alpha.2","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","babel-plugin-transform-inline-environment-variables":"^0.4.3","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"6c47345c89884ebd58d844930d57075ec0bb3061","_id":"effector-forms@0.0.23-alpha.2","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-umEQ40rCOXu/6zvEf7EJg7vGzdBQlREJE+sxCFUrYiD90RqYqbIcQv3wohZH4gr2ORj0T8mDnh+cCow3JV7Y3w==","shasum":"9f1bb277b9d81b1a777964a23af452192deb1cd2","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.2.tgz","fileCount":16,"unpackedSize":271070,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtnnICRA9TVsSAnZWagAAKnYQAIbYqe/tWq+m3rlKW86t\n3vV4yrbWBdIH6z+m5UGb4Ps8ag+4CnEK81tRvKlayqm0E6XmjA3Tdc85lUaD\nrGYFcVBt0Jn3IrR3oheywxqsiwS39RtXVIiR7DbuwDn7vad+ppPKxbn612Jb\n56tthsTvQ5xGz6bW/nYt0rOYj2g+9Uju69UyhmjQqof4pH2XSexOsx67N1fR\n9I2wuXdGom4bSctU9qFWJdR0S09Q1yXed7KWL0HZay6jEYTEyqC8+hMSV9a/\n180EQruK9QXH1aDogNWHIkajT/dGqRz7RXaQ14xkIEfPumn7aMJrC0lUXsbk\nUQN9+GswRp9TJhobeP/xrNT/h3c36Q80Jb4EbdgI61+FzYyy1Te8Llpd4t2U\naT5PZ7Wm4O8bIQxnxNslyI1OO4pq1ldd2plXrULYCIEUogC6s48FibIamWsA\nMCemDwiR0pYvDJwPNV4SX57numobNlcIu9slTZvhSFoXjTYpqTbWmqXNbsGI\n8WH5gSOvj/QaRpWfRLU6TFaSUOd3rOOuDoPGfqPFvLak7Xn7+AAxb1iuizA7\nQoVLkshJQYEMu9VSpE2vdLtR9Edevu1guY3YnltEcfKfVKxMHMmRmj2iNh1u\nJMYCfxJt35fX6EHIWYYxAFjm156GFUNwzTogDwmO/xykmytn1mV9qkjDal2g\nkPGi\r\n=ZE//\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCj5wp8Cv+06MnLv09FBrN78d6nzW0ibAcBtKTtkvZyEgIgNYeA1gJluloT1fUIrZDrkUJ7iCCGo3F/5ewmEYX2ilg="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.2_1639348680118_0.7426568340617243"},"_hasShrinkwrap":false},"0.0.23-alpha.3":{"name":"effector-forms","version":"0.0.23-alpha.3","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts && cp ./dist-types/index.d.ts ./ssr","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","babel-plugin-transform-inline-environment-variables":"^0.4.3","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"37ef57e928d62b5e9cac68eefd97a07bf8bc440d","_id":"effector-forms@0.0.23-alpha.3","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-CxuWHU5uAsUrjTkhSHL74PadeCQYxKj7EJx1GHSlasAf9Ddp+JdQZo1dEjiyJBrB8JwkwZXq04Z3qYWibqJIKA==","shasum":"de3c502a9530ef81628594adbad1a0c61ee98946","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.3.tgz","fileCount":15,"unpackedSize":265296,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtnthCRA9TVsSAnZWagAA1DgP/AhtsUmM8PMb9YGVXrwE\nIWcuXXuGUv1RMRADjBwuIR2Izt8NegnF3leqtlnqOhEPNSymIj1QIUSzymM3\nZW3LJsqfvY0BZorqQ1gglHNebuZ4FGlK4Syz1mJ6liwiLIUiGVtscjbEVHz6\nmxC78HxrF3wqhFz8QQCvhHNRbuxK+Yjw3/9Ev/5DNfSdLwNZCq4SBs7SB++n\nXe3QYfx9RTf7M7QKzi0PxjNsBpdRMaJPgFu1zXZ0mE+qBU9+gZsiWu0RBzpo\nG6PVuY2QaO2OQjcguLApBp/f/2NI/x6uhdT8IKLm971RSsGtOX4eh81W5MAh\nBgX4e9xbYvK7KQi1cUYwnJSLXBq2bOgF0jjomkHjstbGe3aq20/Jpb1L/9aI\nCJzFfXS4JAR35d1P5csShrr9+4DJkJFOcD6Wy8lYoiuVrGtoBSnrfkg+TI5m\nqI15whGLVp/wmgDZkL98weIx/rSOLEN604NbTxKvENucI5F/U994Tx+jLyjI\nx5i7fsNn6h0IERpsPcG885y7zgEhA0lky2knjb58W86+1wVwZ2ABzL6bIj4o\nlWYvKUSfWkD+Gl9w3Y6V0xkAekXwGo08anteEe4cTBSZxE6RQPhqCzJoNQX5\nOl6Q6XPXa6QxKj56NnXSehznWAvITceBInynvflSdKb+WQYR7v60EDtcugoB\na3DL\r\n=GxSl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFZBHgp1101pRZnIHNsYbxoAQUvpzQg4WSHqgf/0mrsvAiEA5KVdDXBvs8q6n6KlmdrBOL5r0o1a4YJfezOwIQDb02g="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.3_1639349088868_0.004372786794920147"},"_hasShrinkwrap":false},"0.0.23-alpha.4":{"name":"effector-forms","version":"0.0.23-alpha.4","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts && cp ./dist-types/index.d.ts ./ssr","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","babel-plugin-transform-inline-environment-variables":"^0.4.3","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"33ea80b89f1dbc3667f4e4fa40a5914ec9f5b787","_id":"effector-forms@0.0.23-alpha.4","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-DB3xPpNehiqWFa1K++zr4mZxOYyB6jTsaeWf/P/iiVnRmUIyKqJ6OW0Bcu4gGSBob90RiLX+WHnDlt+0+nHBEQ==","shasum":"a667b60cbccc511f247fbabeda8b1baf18ea7de0","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.4.tgz","fileCount":16,"unpackedSize":270953,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtnuVCRA9TVsSAnZWagAAO6cP/3pH1JdiOV/Eb3WM8Xsy\n0GKpKFbT5OEsC+vHMM/Rd8q4ZCo6ZlKSlu2yC2GPXenBgeZtCqqywzaDAoeM\nErewXmBOVlOBMhc1LxY+U5f/OPZD9wGPKZxoeMwH7fm9zX9r/95xOWtWqFnE\nafm6/xmpIXKnfKfbyYi6O3PvxIPCbO+EjOC35inaEAG3FBLnU9WPDEm6Doqc\nxQYUhUdDAwFQT4xL40/3R69vmBNPyxpt94TdmwATbw7GGYG4sxQc7P1P2QWm\nvQxxAgPv2WhAim0XlWWk7lgcvJM3BMYb7BYWO6XY8sI3f84UNHWISbUYV5pA\nVIhUKae24OrnEySg8oVyP/iRSaVemNgeX1F6XVvrADYnZnjG9+zxh8h/le1G\nKoJ+nnUj9bElmR3VL0S5aJVqoB7gqaWcUa8Z0poHC9OhqK0JnluZ627mTjYy\nUbuChOT5gkHu2PA360CRdM9hRiA4ls0ov4Fe+7d6aI6+n2or/ekDgP22/wqe\n7Rl7+TKJ9C/ybThL5/lu5d8LhKYL5UyzrRvtmbNuLLhsfKN+IGo6crjrOOFF\nezJI95rcGkYSh/Cn9aObYZ6OTkp2xmg+wyRiXXyysJoTRH/h5i3E3w2SsZ9p\nUSduQnRBCMDmU5LpkHFqzKKHWOR3kY85BTtZ8elgEUXwtgcKXS2all5Zted8\ndLw7\r\n=awgn\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIH70WieRIBARHpcmSOZKm4xocJE4y4RDAUcnBDtFU/f1AiAWQEqc7l00bqwE9hdvEkpvWGK4/g8M1HX/PvDoAeFdAA=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.4_1639349141634_0.8645657141429544"},"_hasShrinkwrap":false},"0.0.23-alpha.5":{"name":"effector-forms","version":"0.0.23-alpha.5","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts && cp ./dist-types/index.d.ts ./ssr","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","babel-plugin-transform-inline-environment-variables":"^0.4.3","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"8c5c697598086507effb89e2c2ff39919c53c7dc","_id":"effector-forms@0.0.23-alpha.5","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-SEcqK5VKnz0h7UwhxOtdLOsK+wHVyTfGvh/0mO4IezRW8eZIob8RRUIB3kcufl9rWQdG97RuY8iuj3Q0WSX4fQ==","shasum":"1edd385503f5b2f20f1d7732f158d48a054060f5","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.5.tgz","fileCount":16,"unpackedSize":270953,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtnytCRA9TVsSAnZWagAASnEP+wW3pFIo/m2GkWxr9HVM\nCC+8kEO5dQ9bvxcDe/cItwzranzB9TJIZhsdhQnis2uWn7vnMWiHK43S2wbm\nGmUIlGrOLxSVYmUoprWtmvS68Z6z7X2nwu+Z07z5RXzTE4dbfhCkK2deSZsD\nNiRriIKcX5EuqD0OGke66ZwcF8rrVEB/Q0aExmsI8CPy4mCHGtFIjlr6xJI8\nG74UpBBtyx47qhb8WYmUleogr364z3VlWnKWsvjnCvYaAvW8+ypZkf6f83QT\nYUkVrGYqk93pl0jiTUeY9bG4kJDNIk0XP8/2p1qqy988JS65z/5egrpFw2o1\nYv+oINwTwVnpcIvw2jnhLeysdOOAbxt4MnAjTDlZWYmRCBvckH/fydcoTNhb\n8WN8012Dl/l6+JF0vddWC5P7wEXHCLwCuEgNHlYkigDgE2xjUhdcXbqP4rsS\nOiXNnWXK/HUUBXLXtK7/D2UkVAG9DzB3v7/PPlnPJw/HLhLsyBuvqnyLrE/Y\n7dBPi+l3dOf+EscJPXlIkY+EFI3oKZsMQTgC/5A+mLcgMf0PcOe0XtYdbtNz\nguwP0NmC0wiExnHrbhEmY/K7I4NIhs9JNtmlw/CpreqHXfqxQjdiur0QQE08\nflBdrxoZYkHvzztW5DPZwX++x5bdK6NDnbqcx+AopCi3V50gK0EH6vpyu7E+\nVjdi\r\n=ZZNk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCUPAPcngJOU/xogAi+zT2kT6IK64PpPeb3lPl5x6vD3wIhANdEvJwgX2IgqDOuufRsB5AEab67Tccnee4i96Kti37i"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.5_1639349420907_0.5190897441027289"},"_hasShrinkwrap":false},"0.0.23-alpha.6":{"name":"effector-forms","version":"0.0.23-alpha.6","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"8f0f942fe8d6422526d9de2a42f87afd822b6187","_id":"effector-forms@0.0.23-alpha.6","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-lP/r+vznKqtXugc3XNTdbmAwAz0z758QUIDB7w5TrYmYbRech/rDzdNfCN8sA01ONK+PTB37bhz7TbPAjFS3Qg==","shasum":"eef99b50928ed6521136bef8017f1d744fcab927","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.6.tgz","fileCount":15,"unpackedSize":265202,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtn+WCRA9TVsSAnZWagAA8JEP/RJoRQRbidPrXkaiLd22\nN5e/ZOk42B9sEjmUL46ar8V78eX124Gd7F1rG+kz8HvAgt67X9R/RTpoSdSa\nh8tAiEUvjb+Oh0mg5f2LwNP4BfWDi/g+gl+0EJOj6agzRWcuZn3RUvthTCcI\n2VsTcnXJU9XxBZwLjYzJz1l1pYbVrkNSCXLbS+y8uYMkGFohhpHehdWNV38Y\nEpJV0aps0pcwEoSyOTVWzR+x2t+1UZuY+tYT5Drv5BHlHspHEyuiLZmQAKp+\nkUpt1eW8Wdq4Omgnw09FMQu1vnZYw1ZJKy1rwE87k/5fZiZcPCyx2h1xEcfY\nY7rvSeIzUD89Nczr69j0T6XXq14KCbk7qbsVSHW1XxzRRhynG3ISYJZDcjCS\nLkAjwcqJfssUf1LBDN0RcLKn53jNFpwLTTD6feTJXTb5qIyFwfQ9f6X1N+wX\ngHSff3V0s4sPG/XAhQNEbqGfL4uvXWFoWvjA86wOIquvSQL9/o7UZ40SWMl9\ngYGnfz2WjnU/OZZBpne8f9mfsq+tbnmpXThJPONP4zvY8JqnsI5cb6f1nYYO\nqbatEvafmhthsqDwPqRgz5iGD4m8j2RVHJa+4nVmO5Kjkar3E1zmKpTwcBoQ\nIGdM/Yj5aYPqMtGLbkQg1hufrOqbQ9vEr0HzDeQAEN948NTN7DSDutvG81gs\nFBIB\r\n=/Fle\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEZ4418VXuTNabGg4f9SGI/w8I3LUW7yItUvQtMXakJNAiBu1Wwuv6DlQ3IxhC6Rqr0CNPS9hyrBbIKgY88aMerKGQ=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.6_1639350166829_0.8893966073116706"},"_hasShrinkwrap":false},"0.0.23-alpha.7":{"name":"effector-forms","version":"0.0.23-alpha.7","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"c7b7efcaee4c7bffe6ffe37a6114a14d6031225c","_id":"effector-forms@0.0.23-alpha.7","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-cARsmsWgi5jazEEBL3L2zHav9HSLGYdIW09g04/H4RCq1ryuB/1F3eHeagdnXBrz7QSNpJ0yGuV/kHIDsB5zCw==","shasum":"638d64fb5fa9cf6e2ff473e113c1d7676eeb62a2","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.7.tgz","fileCount":16,"unpackedSize":265236,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtoABCRA9TVsSAnZWagAAVfUP/0M1t35VDX6cfVX4lQPu\nsJv+nZv+Tgb9wA+KplyqBgx8iPXR/rXl1tIHPNCC8IyxqrAtwU5puh0NFaQc\noXuqbUuBT41w9rS0ULLXN0t+DQ621hL3Bs6jximWcg1Eq22hKnO1DLJEi7fN\npZg6+EB4rlt8ZXBliQ09t2PHJZlVH5p/5V/3l0AXlPxpjouwKdmjex3sNzth\nO7v4lU6ikZNK7wIKZTL3YfQiG7+6nBnj+hrAxCP1nyhezvFIt5q+3LCxRuSG\nXWV5oxZ14dXAhR0/Uw8ZkQy0PFvfqD+zMv6SrQl8Oj6mHetDc3ZmUxicd5yU\nkCqKqEvMPGg6tVtct4/SYx1zoRzkTFisB5afCkjLQFlIRL/GixO5ftMFwamJ\nKwiaHHtQpY+TeFp61fKjqV8dt35mCe+2uZqGwIUJCfCq5Jv8Z8oP4AnVmiJO\n1LhTNp8EYYDXvmak62Xf2XQApkrKTy8LNjtL2FT7atnuwcph2woEDSqzkeOL\n5nGKrSUUUaU+fOIZpEzrKAr1Bn89VdWgSy5tCZ92dF+KV6hLIqBjDUrNdlGx\nRE5Lgr4CP6/zn1yvXHmFnfLpFdkZ2sdLLbNOLbrmsIXHYW6By+BjXlAiID3L\nBdKV9uSI3PZEDQFUo4Z8B2HX5JhoivaV9xEoTs5aplRPvMPgLbnn+wShFWQW\nijDr\r\n=wASf\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDFR2gTTqIDNFy7BQkMSZpN+1IrS2hZOnokLQtHaOR4fQIhAIxLcqgesLHCrOTxCNG8wh0ZamwW48FjNDGh6Lu5MlCK"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.7_1639350273243_0.4325979600780434"},"_hasShrinkwrap":false},"0.0.23-alpha.8":{"name":"effector-forms","version":"0.0.23-alpha.8","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:nossr":"cross-env SSR=false NODE_ENV=production rollup -c rollup.config.js","build:ssr":"cross-env SSR=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:nossr && npm run build:ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env SSR_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":"^21.8.12","effector-react":"^21.3.3"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.5.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"3644065d4effd0419a15d42560bef9c5af00e01d","_id":"effector-forms@0.0.23-alpha.8","_nodeVersion":"12.13.1","_npmVersion":"8.1.4","dist":{"integrity":"sha512-E2qA/NbG3ADwgUMiSt/f6SDjJZKQWo1Z61kXRF+QHOGr6j+K3yFWW+gfME4tVt4yuY6KMBJVWbIuWd9Uf/k2Mg==","shasum":"50cab837e2646f23d72be3532b24b57cd582e925","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.8.tgz","fileCount":16,"unpackedSize":257718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh78VnCRA9TVsSAnZWagAAcVUP/RDNrkA4F9btNR6VfOQR\n3F1YsaUD9LIzBTSA6pW80Ap+GU6n4Y0vBMaX9toyX8kB96rBMsnLC5O1fn7C\nk9yzvp4uIzu//3fv4/6v4ROdCJkB7RsJAyufXUdj+TaKeAaYqHTpgmth2NQ3\nZrJR+Mo9XInS84OC5c1Y0Ig9YuOnOqQ+o0smA77auonkkqSbK8JbB7j3nUKp\nsN2bwY9QglZ4THNMdk6bcA1WXMpPF5+od+Za8zlcDva5krcsiAVEKYSrlYzk\ngDKXbqFS8O36MgaCR2Ggd0/a2pRvnNmCxfzU+I1tn7CsWUeyY1mKaVoDMbfm\nYp/56eu4OyAikvp2wV8m955IN70q5LmeC/R8XQ3nwdMm1So9DLOngQNoS4gW\njnBWLL0CqHNt232qbF/5bNH2YwZbN4UGQkwkphRR0+iXZIxJIr4EAUmdUbrZ\nzhuLgEQztn3WGkFUeYo7LqIxWa542EYBWuUVUGiKvXGCkrwsYMSw0WCA7BHg\n4cbIis9aeCxExqvAhpYj1ebVQGZ2n3aiUZ18dQEf/B+QFcRwzv3JlIwE+Y04\nRCXkbnoSzYQnxfGLjYYOSdjqb54UFf99+97VLqoaeL5N6D6g6jUpag+ezTnm\n/fu4ActJCgrvOer06mTfexQGLUlO6BeIXjyaRElaKv5L15ruXHTH9bKYS8xW\nwBpR\r\n=95RX\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC2Q7WY0vQPUFvVIDCcUpLMMteva566MohAGrjuDEfghwIgWcO1nj2xcQ7P+iUAfTa3WtAzpTJI9cig/fRsrYoq4ug="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.8_1643103591429_0.27677454259571266"},"_hasShrinkwrap":false},"0.0.23-alpha.9":{"name":"effector-forms","version":"0.0.23-alpha.9","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=22.0.0 <23.0.0","effector-react":">=22.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"957dde8ac6a58dfc720bb6ad23096a3f35d1bcf8","_id":"effector-forms@0.0.23-alpha.9","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-oRFbAdRRqW5ytQjuj6Sm9etflWu0GYItYYV26eIl1RL9SkS2e9EECrRNKDxJSFS8lcIxowDP3+kCQIY5zucdSw==","shasum":"aad4ed7919a50e15b2325b7a08a95e9c3c72c269","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.9.tgz","fileCount":16,"unpackedSize":423742,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCBzuCjSf7e6G51yH3x3RifhhI3tHKUtZyOdntdJgs7iQIgfwnRVogjEHVa/cArObnZ39k+jcb2XFWDWZo2ksrviBs="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiXID5ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoqfxAAk3JzUalUL+ZMW/+L0i/vZy5/zExh38+36i20CkeVuttkNvgP\r\nZxR97srjnvUd6Jq7wwEVHR1yWn5W+8HKNHo4lA3jWFQA6qKTO7eJInOpOnYW\r\nTq5OKRPGbmTHbvw6YPCfSIR/eFSaBgj73RVkqqr42i0DBYc76KhHxkCIFRod\r\ns/DgaE7v+UywgkxZOkw/SPdRySQqFNCRPPTx15zLDH8KYmIEEFa/uBHL+Wpu\r\nYU5q5tKp9SN8RVAZLps468rtW1xa61Z3AawbuBMr3H2tFihcQwqIb69vz0hS\r\nAFpqipc/oEm2vF/cU0Q5VqQbB1mATs0rbzUjbcCxtJHtDoG67DIks093i5F7\r\nqrfcEVxByVMKyG3D/B6j6Ob6O6OXhfAMURBGfRtQ1KNpxPhDbh8nUzVO/3FG\r\nZ0IPdX0FSUs3mfrkEbwqvqCJhWTWTYnUcwl29IBaOwy7PjioPXVR4EmQyJMC\r\neM9vSlHZNU2ip3bH4T3vt66CnUQWo1u9Il0eapJXC9enZUaZXlxX+WZmkHog\r\nR/Xu+T4BPHtBIugicbGUA4ZE7+Mtc03bhTvRxTSBmcUMCimwCB57tmD6/NRu\r\n/+0bsCxZj0JgUdyUp+jtaapdIsxxlsDskvivXJk1MyZwnfuPN6eHhK6KRJ26\r\n5dgoVlwfDYiqPTAeGs1y4KClO9gFj4MLp1E=\r\n=ZaFq\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.9_1650229497056_0.7398366332141468"},"_hasShrinkwrap":false},"0.0.23-alpha.10":{"name":"effector-forms","version":"0.0.23-alpha.10","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=22.0.0 <23.0.0","effector-react":">=22.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Coming soon](#coming-soon)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n## Coming soon\n\n* dynamic fields\n* async (effect) validators\n","readmeFilename":"README.md","gitHead":"6b67635c68b8973b72dc3d1b911a99850cf6c6e0","_id":"effector-forms@0.0.23-alpha.10","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-D6BV+YSL8jmd6jzm5aazTcHuZesTxzKgxrBPWdZRAo/0huZeZyaJs46mONdKYchKc+7VKEoOH3/6GL8r4j1EkQ==","shasum":"9d8d14990f9e436159f9a244804457965db298cc","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-alpha.10.tgz","fileCount":16,"unpackedSize":275873,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDOGyo4RGJwxyYoOniyEZpW5EJJNr3yO1rPw+JAUNRPMAiAxwFZ2qrLIwhynW4Ble5qvdqp8eNcqNap28m96IBpUww=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiXIMaACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrSTRAAowSXxsywFPuFIJnGbOgCDqY1BxioLIAZrh4sSN7CVHo/RakZ\r\nsisOt9BENEZ2J0jEzHuXGVRB5LBf5Yr87KS1A/tY7+KaR9bN2D9/SRoywpTa\r\nverr0j9Am/DRTMxrggNkKCcZrz7RdSGLXnj/xV9+VRq1E6U/FYk/MujToY+a\r\nB5JvcIra/Fx7zgr9QURaYhyG60qqk+jjbsP3ZLgsJdZoiJiWKuK5qi+VW0pv\r\nykVVY32AfKTNNvw9z0yYprLy+GdwoQawtMDmuYlczevkHXpDNfFsxrLHdWuz\r\nEpYu5UcxpqR9X+bRwh2ian+MoSkTJuq0X9ctheeuuVSI526dzJoABKUprGYw\r\nV2d+4GLjcblGrwFyeTG3nCrZRHdOrX7wpROmYdqKCxXHWJXJDNpt0v53/Gfc\r\nshv37O05cWTDQcWDz0zHMCOvOay2YJeybUdqipGDldceUY6Mw1qrIBAOm1xY\r\np2iRbrnv6hgEgCzkXwtMAoOFMrYuowVX8SY8eY4Suoxfnxbm8r2W2vheQlIa\r\ncfqhfsI+mw0OdjbFdi6UFPzKZHXwqdZgnhi5qvXCOH3NM67LdTfI4Py0bISc\r\nQBNII90yR4XV/vH+xMJvy5KXnvhiRvFkXT6jE8Y3qOztYPlE5QHExb4cB6ef\r\nwcE5MqmQXK8u46qZHQpC0TYWLUPm45bOwwM=\r\n=lTsN\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-alpha.10_1650230041967_0.06322646729154902"},"_hasShrinkwrap":false},"0.0.23-beta.1":{"name":"effector-forms","version":"0.0.23-beta.1","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build:legacy-ssr":"cross-env SCOPE=true LEGACY_SSR_ALIAS=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run build:legacy-ssr && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [SSR](#ssr)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n# SSR\n\nIf used with server rendering, import factories from *effector-forms/scope* (effector 22):\n\n```ts\nimport {} from 'effector-forms/scope' \n```\n\nFor effector 21, use the alias \"effector-forms/ssr\" instead\n\n","readmeFilename":"README.md","gitHead":"0c9d7963aa94b7afb1152168df0d97d8b5e9c97a","_id":"effector-forms@0.0.23-beta.1","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-7K/lbkIdoCE9zlWHmEJAUzWQ5T4ySl/X8NObm+Gyk5gNLGqUN1udrQKoaO4q5j/391Ze8rKvxdT9elS/B/FYHA==","shasum":"a12ca2c621d964ffed3a76d350da85d67d660394","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-beta.1.tgz","fileCount":21,"unpackedSize":554833,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCYEkdLYDR+9M/WZRInK54QvFnT8oR2LbbLiZCK5PAfEwIgf11Kg1MJx4TTqzK2RTklCqoYRGhnh9r2kse/G7wwFpQ="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiXJugACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqtfxAAgnHtCvq3d2WAtXrcY8uR8ddkQFN2xba1Me+MqKByZ09bBZCe\r\nEsewo25DL965oeytgTD4hHysJ45qAUEiFbTHIXQIp/Q8cad7DOqW/WzIq/zK\r\nKImPWXYLRRbd69fYkauDJbCevp2/gzs5IZ+TsHSbit2AjrM4b8icifV+XFUW\r\nMhSq/PkuKVQ1EExhF9i9IoI+LsjQBMED/G79JG4b55zbyqGr5TtWnHPkEw5S\r\nuqAI6cn/ym+87nRlUJ1dfzDLshlQd35j7BmWzrNQd+1p5S3MJUR+v3Of0O/k\r\nQu9y9r5nVKsCYhBeSBKgML7+UZWYRZ78X8o6eWI2jFbtiAO4v2f9lwqW6vYQ\r\nBlYXTQBSJ1o9WtCosGYKFTif4NLpmt6wWb+vO4sNWZUn3RKY+MltgWdB06zT\r\nJxA8ZODbpXZ0HsoCG/Uj6+Cv79K/joMjqquUEXbeRlaiqSugGQlrBfTz4mg3\r\nEivX7WI/Y1cKCewpI3Is3OTE0+6s0W/2Hg+EKXiQ5ZgOxzRsYYsKaSHMO3at\r\nIUPhRdFG5dLc38Rhu/yoTOMiUUVa8EfV4Xyo4HNpj5wtIUMtha+W6KSgBFQa\r\niG8rKEO81aEye5e1ozQuWPTQg8MlxlffrrFFF1EJ5oTK67nqu0WwAZm6Rys3\r\nA+0YQ4SEhpgkeO8jbBtIsM65hP2ElMbiqrM=\r\n=zGcD\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-beta.1_1650236320051_0.6276545629113088"},"_hasShrinkwrap":false},"0.0.23-beta.2":{"name":"effector-forms","version":"0.0.23-beta.2","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build:legacy-ssr":"cross-env SCOPE=true LEGACY_SSR_ALIAS=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [SSR](#ssr)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n# SSR\n\nIf used with server rendering, import factories from *effector-forms/scope* (effector 22):\n\n```ts\nimport {} from 'effector-forms/scope' \n```\n\nFor effector 21, use the alias \"effector-forms/ssr\" instead\n\n","readmeFilename":"README.md","gitHead":"3e36396573ff59e7dec254978efbd7a319fbeb80","_id":"effector-forms@0.0.23-beta.2","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-oUQ5jGpZgXXW+VCvFOP6WiRoIzeOCqqc2jvm2cp7bopIGI/d8yEiWaoYKiZzivMOjnUCSuhcU7cWW3tT4hrn1A==","shasum":"05e0a6e59401b117354d1826226feaacea17a936","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-beta.2.tgz","fileCount":21,"unpackedSize":359871,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDFEcKunmpbrhKfv3/ecKriYfuGgzwLmHd0HGC74C+6swIgXKiQ9l8Es/FuUrhwurbx5GULl66W9tjjwgXMCU/DL6I="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiXJ/RACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqjlBAAmSn3k5YgDGQ4DLWH6Rvpinova3DH34HDAYRF6z59ZLCBhPgC\r\nNMwZa6ClDWmPHyPJnV//Px3/yq4lKmugutSwOmIl5YsVa8rgvIr/MDdL+A1D\r\nu6k3Hc3X4qsYeXcVI9DhFFXkp+U7V8WMEhDKJPqSsQH7vqQQfl+k85KFVuNm\r\nUQ0jw/jqX0sjJaCotcQDwPDbJBStABXkjN9wuINBge6JdHZrpsQy1OPMmGI8\r\nBGJWBn83RiixiBQp3wzVE3ku0/9C9AUyZJBMdnWqTrGKREydZSc83+bm/cja\r\nyDvNNXjdEaLyxjeIB77ZcM2jsLsu5tUn4LgtndDI6BRMAwkQTYcTTbX/ulPm\r\nNMtZi7KIBRj0s6e38htJtrDbQ9j1ZQPOmjejdCQROYPZ58nE3A27EUy0e95V\r\nN8NtY2eTTscohVOGzbZcrSMt8FJyuzdIbukLgY98ciV6yzr6rgvLx6Aq0I6O\r\nbgzCbWa/29cSIVykr/dRSn2GWniUq8XV4lo6LI266c7Gx/Y8Fnm348TQNbRx\r\n3QALbqh5adJPRHnuFPzrMWZnn1+nLOQkcC1J7KS/qQoq4ncju4pfFoDwRPiH\r\nZchlYircfGJatoKs9WUxG3QXEDpMrBF0bmOoBBuuWvBzwknVcmJXefWIsaRq\r\nKHaNMxknzuzu7v6lRVLPO7iRFdXJ+pPNK0s=\r\n=vGxr\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-beta.2_1650237393790_0.8184732514995392"},"_hasShrinkwrap":false},"0.0.23-beta.3":{"name":"effector-forms","version":"0.0.23-beta.3","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./legacy":{"import":"./dist-legacy/index.js","require":"./dist-legacy/index.cjs.js","node":"./dist-legacy/index.cjs.js","default":"./dist-legacy/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build:legacy-ssr":"cross-env SCOPE=true LEGACY_SSR_ALIAS=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [SSR](#ssr)\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n# SSR\n\nIf used with server rendering, import factories from *effector-forms/scope* (effector 22):\n\n```ts\nimport {} from 'effector-forms/scope' \n```\n\nFor effector 21, use the alias \"effector-forms/ssr\" instead\n\n","readmeFilename":"README.md","gitHead":"e14368ce769b3db2dc9086aa641d43df6857ec9d","_id":"effector-forms@0.0.23-beta.3","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-iABKA4dlJhmd2yQCyJCAUrnutIfC60ccQFU4FASOiV4DCA8IvjvdD620qxhZmUQCTIpX2G1bq00ckYA4POvypA==","shasum":"7e7e4b673311fd883c188a08e0bb29e415781b42","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-beta.3.tgz","fileCount":25,"unpackedSize":440900,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEOpCXqTAJ72gMMxR/BvXSBK7dP9E5d/ltxDSMR8V415AiEAqBQy2pWV5GW8zgDTx2Bokh5w+H8x5YHsWAq/CCP84w8="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiXKPDACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrBpg/+J1kgs6xiFjG8oZuCy1aCv5+hE9kGhNcy9cr3sWUvFCVwSuH+\r\n432LFIxSrGtxaQtkLQKFtDfnkEdJ6sZ4huY0y5JY76ph9tLJM9F95JoD2y8w\r\nA4UUGwwLppzLqRMUIJMmWEZ9TfRLGmxAdB7FiJEc3BGvdlwgqS+2tF99SXiN\r\nyiLBaQ4eaOrhATjtStu2zRLIhoPDXYdxLxgMWltjwu89HBMSa6wgQXRhJCZ+\r\nHx0rngWSQQGW6y5sbvIfctBRIRJjZ46sEVMInm7wx3XPL5JEp7WJFhaA7QAQ\r\n4dXGzaG5OmMadN5WA7r2SkHO/mJNbk6EtbMHFuxWfVlzG+17e+agJWrUgZnx\r\nNnh+T++TQF8pqBKrpu+z2p+3F/dO0ueHhyBMTabZVITu0iKYqVdgYK1XKlTB\r\nhRpvk+ipudVKfB5iatIdI5LXmtHB7BpWNykP/8T0Hxi/tkyAQdKC2BuneNpC\r\nIi3BV6yNsaeV/eUg0wie+Fv+KsGDpJVDN9eEoFU2k5RZ4dnUeosKHPuqMRDB\r\nDpi7I26Xh2y2orYl5zV2K4yd2VlVPgHyMS+PupikUOe1c/1BBwBxHEk12Nf1\r\n4+XZl6i2E1n0ljZgOLm7io+/TpYwgfnhuhBetkrQTORVNFxZMKPbt3oe9KlC\r\n8ssNNBLa5KhHQsOg2FenEVU29ojKT338zvw=\r\n=EXID\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-beta.3_1650238402952_0.019945684440811906"},"_hasShrinkwrap":false},"0.0.23-beta.4":{"name":"effector-forms","version":"0.0.23-beta.4","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./legacy":{"import":"./dist-legacy/index.js","require":"./dist-legacy/index.cjs.js","node":"./dist-legacy/index.cjs.js","default":"./dist-legacy/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build:legacy-ssr":"cross-env SCOPE=true LEGACY_SSR_ALIAS=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [Effector 21](#effector-21)\n- [SSR](#ssr)\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n\n## Effector 21\n\nFor effector 21 use alias:\n```ts\nimport { createForm } from \"effector-forms/legacy\"\n```\n\n# SSR\n\nIf used with server side rendering, import factories from *effector-forms/scope* (effector 22):\n\n```ts\nimport { createForm } from 'effector-forms/scope' \n```\n\nFor effector 21 (with SSR), use the alias \"effector-forms/ssr\" instead\n\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n\n\n","readmeFilename":"README.md","gitHead":"599ac2352b8707e34392f4f5cbb3e358612ca9dd","_id":"effector-forms@0.0.23-beta.4","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-fWT6PUtJb+Br/jVSaBnGmISOGk60cvT2vZErtGJ+I23/tLhqp0GDuXKjr6I3d2LAaVXm1yEX1jO1VJzUGLHRGw==","shasum":"0226eec3e0b588c65ada8b1ec2140630cbcb2d91","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.23-beta.4.tgz","fileCount":26,"unpackedSize":441113,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCt/pTRU158BeksTIiHhWyvROqHWUiWAQVujifldtekWQIgLIZX6j+NSb99it9hBfrCbRzxCdoDnvhXQfTqPfbAWr8="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiXKWeACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmquLhAAi7Ge5njC6P3XIDizvaGcVE4IZfEEgOYAoZ1+ePQmBHSICFQN\r\nUPU7PwJOe4QTx4MGB/DVu4CueHh17e29aPHQk2eGE8w5+zPA0QK78Qg4ifcD\r\ns8jow1lNB31bdu4x7V6JCq5Vv37wjuDiuzEksUvSCtDHihalV/BEIKuiTh9O\r\n/G0dnnmPbrGE3eYyPZV4EqaO/WFEM4DGGRKW8rizI5M6WQZLXVE2nrRu/X/l\r\nBTnqQTXreixSva33rIpV9q+yyEC+9GfaUl/qt/5WATLkP/w+58k1rtaQa0s0\r\neZLDhb9IT7hBp1Xk1RXH5K4lzfVe3t/GB7VnZ0FDfvP7WbJ7fWEVfdgURPOU\r\nojA6D7+4BRXKTA6KWm6thkaBn1ifs0/IKNqt5PNcceI/MIiRiHgXszDJQyeD\r\n11z/epXHhcrYPH+K6UM6LFm6UPsDhoJNnL8Mo2wSmbDlB34NCRu9Ablwejst\r\n6MqFtyzr1MFvhaM14zz/7YwmH2MBj9zZcu04on3/X2MG6xDTGIwedm3H/36T\r\nj2XvuAnmMnvICl9MxCLGPBbX9twlaGRXZPruru8xNWXfDqQasgxSMLY8/7a5\r\ns75yUGruhauFOCzQpLWecUmU5l9CbGbSQM6/FaPVVm/5ipJqVvxv7QTd60dM\r\n9gTfM1n2s6nw0pxdUsUEPGTgswLbb/FMbFE=\r\n=0JOT\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.23-beta.4_1650238877939_0.3126635149113204"},"_hasShrinkwrap":false},"0.0.24":{"name":"effector-forms","version":"0.0.24","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./legacy":{"import":"./dist-legacy/index.js","require":"./dist-legacy/index.cjs.js","node":"./dist-legacy/index.cjs.js","default":"./dist-legacy/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"},"./ssr":{"import":"./dist-ssr/index.js","require":"./dist-ssr/index.cjs.js","node":"./dist-ssr/index.cjs.js","default":"./dist-ssr/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build:legacy-ssr":"cross-env SCOPE=true LEGACY_SSR_ALIAS=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"c462d2ebefc5c73d3aa664482c275815bc66195c","_id":"effector-forms@0.0.24","_nodeVersion":"16.13.0","_npmVersion":"8.1.4","dist":{"integrity":"sha512-bFExfHd2WKwAuDbCQ6kdGU/5ivx+C3N6cEcLBXcdTJefpnh5D0+Sp59ai8+xV+xUEVC1RTB1jA8WCeYbgG04fQ==","shasum":"2ff2ee251b4aa059baa9f6883866da0758a896bb","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.24.tgz","fileCount":26,"unpackedSize":441106,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDIsQMIO6wu3S8DEFp8tSp/SBrGXy0xLD8/VIEGn8gotAIgOcdFBchtH7rBPTUO1Y52x9lAa1lqx46Pt41AeTaLKVk="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJig7XtACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq2xQ//a1k36RoEzoYrhNxFOxYlVgj0enAByk/A5VLk5Hroxh7gY0ag\r\nm/PhGb1rdTIjFEPqoDfSljgQ4YFadgz7w0Xss3xxGvrU3bdUiDsHOnMcAGpQ\r\n1sc1WHOfTZxnyWx7cJskJjMKhNztpM/0wHrz2SdAshwX74/U+CToxhn1+YBk\r\n3C8GzCsa+CGMV4qyXeJAxLpgKEv5lBeQ45vrGezFO5v3Mq8dPPRd+VduS51w\r\npAEJmJ8yp2FTHoU6BcywxROiO7KwmVipXhbGx3/NNKLttRH3Z0YiLcKBb3IY\r\n6hE+O7f1PgJsNlZ314hiU3qAEdeqOO8oVD+BcfpyCJGSUxb0C6yASjHxOrJm\r\nr5/AT8zDO8/NOmrtIzGAJxy/7m+mZ542NVBtmgekT605ZindYVldzfmoRV3s\r\nketSFDBnfAEWzioBRaLzZs5cPhVL/kfuaPZyRHWxwaQdRgX+ovK2wMHzFlh6\r\nZef9Wvv6lA5jfOpPF8QfK54xM5iZAFv8Mjf/Yxh+7dbRBYtuGidhGTmJQqAg\r\nqAJvP7g6ZDDDPshnuDp74DFlVbU/OhqTtB8PHPKAhCrIx1ZUOY1UqYm3v6FI\r\nIY8x17Uf6NeOEf9xPpya1jRakl2YBtmPVqDGojaY7QnK5jsbRfkI4w2J/WqH\r\ntXzwnzFMKEs2F/Ju2qwh2OS9TUX5wHJ/OMk=\r\n=tNIg\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.24_1652798957630_0.683013725025756"},"_hasShrinkwrap":false},"0.0.25-beta.1":{"name":"effector-forms","version":"0.0.25-beta.1","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [SSR](#ssr)\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Effector 21](#effector-21)\n\n# SSR\n\nIf used with server side rendering, import factories from *effector-forms/scope* (effector 22):\n\n```ts\nimport { createForm } from 'effector-forms/scope' \n```\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n## Effector 21\n\nEffector 21 is no longer supported. For effector 21, use version effector-forms@0.0.24 and earlier\n\n```ts\nimport { createForm } from \"effector-forms/legacy\"\n```\n\nFor effector 21 with SSR, use the alias \"effector-forms/ssr\" instead\n\n\n\n","readmeFilename":"README.md","gitHead":"514e735d3395150eb117bef04ae82a5a9c826a7b","_id":"effector-forms@0.0.25-beta.1","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-oIA9hIIKdichQ1oRsb2/u0aMmoUaaeqnE72RrnrMJFMtTdxbIHflKj5noDrMD6HKwPZFRuvkNxwsTvpGvMMlgw==","shasum":"37baac201a277b2cb8f4102a735dfb8521b0d784","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-0.0.25-beta.1.tgz","fileCount":16,"unpackedSize":276249,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC6/emK8BU3UfXx00Jwu+B9s+RmvCe1iTEzJH/tuevGeQIgJsUIZpbQpx80bvRE6ZsZdtn2YtWPdDfjuw10E/FzqBg="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_0.0.25-beta.1_1683975755911_0.8638329970881049"},"_hasShrinkwrap":false},"1.1.0-beta":{"name":"effector-forms","version":"1.1.0-beta","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","readme":"# Effector forms\n\n- [SSR](#ssr)\n- [Motivation](#motivation)\n- [Usage](#usage)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Set initial form](#setInitialForm)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Bulk errors](#bulk-errors)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Effector 21](#effector-21)\n\n# SSR\n\nIf used with server side rendering, import factories from *effector-forms/scope* (effector 22):\n\n```ts\nimport { createForm } from 'effector-forms/scope' \n```\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n## Usage\n\nModel:\n```ts\nimport { restore, forward, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nforward({\n    from: loginForm.formValidated,\n    to: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useStore } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useStore(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nforward({\n  from: form.fields.email.onChange,\n  to: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nforward({\n    from: form.formValidated,\n    to: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nforward({\n  from: getUserProfileFx.doneData,\n  to: form.setForm,\n})\n```\n\n## setInitialForm\n\nIn case you get values from the backend, use the `form.setInitialForm` event instead of `form.setForm`. This event changes both the value and the initial values. The `$isDirty` flag will be false.\n\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  to: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nguard({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Bulk errors\n\nYou can add multiple errors for many fields at once with the form.addErrors event:\n\n```ts\nguard({\n  clock: loginFx.failData.map(\n    (errors) => errors.map((err) => ({\n      field: err.field,\n      rule: \"backend\",\n      errorText: err.msg,\n    }))\n  ),\n  filter: (errors) => errors.length > 0,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nforward({\n  from: somethingHappened,\n  to: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nforward({\n  from: somethingHappened,\n  to: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nforward({\n    from: registerForm.formValidated,\n    to: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useStore } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useStore(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n## Effector 21\n\nEffector 21 is no longer supported. For effector 21, use version effector-forms@0.0.24 and earlier\n\n```ts\nimport { createForm } from \"effector-forms/legacy\"\n```\n\nFor effector 21 with SSR, use the alias \"effector-forms/ssr\" instead\n\n\n\n","readmeFilename":"README.md","gitHead":"e4e7609dedcba4165d280c6c945d0b5b637bac05","_id":"effector-forms@1.1.0-beta","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-WIEr0JNr54R308DURJ3ba3KeVBIhz9RpV0kmv2wyr2lXJoJJrWlN+M/CzZJ532dh5p754nvM2DvbNeH1bspCGw==","shasum":"9c3018b9698c340507c29989c05d25dd8752da8a","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.1.0-beta.tgz","fileCount":16,"unpackedSize":300492,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIB4nu7Xgd0KPX6xr5txojO6j6gxyCnf4ablY2hflQs9UAiAWTl3yi/Mh3y0DjBcNW6AB/52cvkdGU1vjpGAkUK3bZQ=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.1.0-beta_1683993613141_0.8885026540287542"},"_hasShrinkwrap":false},"1.1.0":{"name":"effector-forms","version":"1.1.0","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"a8b9a66c99e69d5cb96f3c8b621844931e38f484","_id":"effector-forms@1.1.0","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-vnds/xt5Ax9rEcPwhNzMA5htXK4MKNeevfUgEhfE3PNf0EhuamkOP/0kQwdMznVINJhWxzZi0Jt2QekDqkfCPQ==","shasum":"9eb6924423dbedfc910a9f59ad22980afa85aed6","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.1.0.tgz","fileCount":16,"unpackedSize":300487,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCMQwMTqfxTiK8ACqBEnySfkE3vl8QN6oIc0eGdIuDkZAIhAJLsZLCZ1GXLvErmdamj6kkTdnnmHkokrPs3b2nSDghJ"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.1.0_1683994148361_0.14430154867494394"},"_hasShrinkwrap":false},"1.2.0":{"name":"effector-forms","version":"1.2.0","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"51a35d010b253fe025e42420608b6d422e314ea5","_id":"effector-forms@1.2.0","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-x2HzDC3n6EZRxaDV7HsRvHxdqsJBfHJHDPpnWpr4F5R0/bOoTG1pKrQ8FtaECTIvaHBH7xdVpkTxUE6do41BpA==","shasum":"65f82f59b9e54d9d0f4890e8dca8b1899a8e9ed2","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.2.0.tgz","fileCount":16,"unpackedSize":301182,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDN7GTl8OBBchwiQ49PqvDPlUJVJt1xAL2MOqxMbw7XwAIhAIZpfx3/xxLHBBLb2lsLoO0YWvPe690Xa/GQC6vS7kq8"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.2.0_1684010537361_0.3508993088022112"},"_hasShrinkwrap":false},"1.2.1":{"name":"effector-forms","version":"1.2.1","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"c1a1229e9da3e613084876cb3b8d9f02ee662c79","_id":"effector-forms@1.2.1","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-E6yJ0iVFWf0Gsk6oKjJjfevNRPa/d5N2J/KbQSyVPhTHLw4mdOrCOUmlv00z4qt+oDEBrUfqxSAwd0NyLNO0FQ==","shasum":"b735fc28fe28a4334dfce87aa02b9d60f2c6ad77","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.2.1.tgz","fileCount":16,"unpackedSize":301228,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDIgY9smgU7WA1gvuSkCqcqPw717YMK9fQpJLCjGTjP2AiEA1s4U7yJpkhD7EgVm1fM1WjzRiFFE/ZIrRwt9Tqn34s8="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.2.1_1684012823960_0.39879529119918633"},"_hasShrinkwrap":false},"1.2.2":{"name":"effector-forms","version":"1.2.2","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"e5662b85fcc3151c9941319469b44788c212c150","_id":"effector-forms@1.2.2","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-khiUq5LpztAvnxPUgGBZNF/neW9KCmR7fF/7lX+B/Bl9rp5m8qSd4oBy71gHcmotDEnNNJGh6us9lCHI6sGkFA==","shasum":"c2a761ca744f9e2a00cab64fa1f14c3d4466f4bd","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.2.2.tgz","fileCount":16,"unpackedSize":303846,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCBWZjeckmOw/e8Ya04pscssxEnS7GfFrFHBgjQithkqQIhALsNsY+csDc8WIBvfotY+Se5SBc6PTMjQovxuHCoa+ge"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.2.2_1684013687599_0.13073641360890997"},"_hasShrinkwrap":false},"1.2.3":{"name":"effector-forms","version":"1.2.3","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"16d85038909551c4e0c40b14eaaedf99f8a0e0a1","_id":"effector-forms@1.2.3","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-JERHuKslIRk7bof3Y2eU1/QhPpYM8sb1Fm6gXZeMU7al5onK/t+/ltxDkdZ3IexnYf/rTeu/N3WO1OnG4oHC2Q==","shasum":"5d658bcc5968a4e74d31957966109d42436dd4d8","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.2.3.tgz","fileCount":16,"unpackedSize":302296,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDHe29Fx3BVZvseRlEcH4tU0YO0L0De9tdefbLGLGRxBgIhAIp/9vw4fzi4j5ft4TYNnv18m4fxFcz8N0H3EqaE194N"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.2.3_1684014635130_0.4740385308427393"},"_hasShrinkwrap":false},"1.2.4":{"name":"effector-forms","version":"1.2.4","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"},"./scope":{"import":"./dist-scope/index.js","require":"./dist-scope/index.cjs.js","node":"./dist-scope/index.cjs.js","default":"./dist-scope/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build:noscope":"cross-env SCOPE=false NODE_ENV=production rollup -c rollup.config.js","build:scope":"cross-env SCOPE=true NODE_ENV=production rollup -c rollup.config.js","build":"npm run build:noscope && npm run build:scope && npm run emit-dts","check-types":"tsc --noEmit","test":"cross-env IS_SCOPE_BUILD=false jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=21.0.0 <23.0.0","effector-react":">=21.0.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"19c72ce7531f39ed861db9febbfc25eb8a8867ca","_id":"effector-forms@1.2.4","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-gPmRJEG/OqSN0EVj82oaaW7HsoyN/zXHvsVTGVZkQTzB8u+9W0Cx2+cHoStr/b/c7a6Obgdx85TD5XAR2kiTfw==","shasum":"ff69b12fc9290a3419118b5a3ab4500df9f1c21f","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.2.4.tgz","fileCount":16,"unpackedSize":301014,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIELy93ZU5yE3iXClbUxffkxkVM11IC8qlrynU840hmTPAiB/fHA8nQoDKT3xqOoQGiAYKICSxzLyTU42pOHa8A6IXg=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.2.4_1684054120153_0.27495397173485525"},"_hasShrinkwrap":false},"1.3.0":{"name":"effector-forms","version":"1.3.0","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build":"cross-env NODE_ENV=production rollup -c rollup.config.js && npm run emit-dts","check-types":"tsc --noEmit","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=22.0.0 <23.0.0","effector-react":">=22.2.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^4.1.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"949f00baa90b1cbcc597694f739a7e1a99f91344","_id":"effector-forms@1.3.0","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-RLXfABYCsPv14JEjt2NiQ5yJOEhBxQEPBfucwe6m3S8g0gau2vBS6yJseoySTipUT5Jwx3zNknD5BK8LqrOn3Q==","shasum":"4da087e74cb2f9bcd6329c5c9ed727b4dca45dd6","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.3.0.tgz","fileCount":11,"unpackedSize":223041,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIB5qF9a7JH6JqEsWkyPH/d/lAgI+UXD6/+xlltyNXdwiAiAxgqigAPBQn6jrUwwXpVE4CA7acdwjaXSnUUmQQlkLOA=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.3.0_1684073791995_0.7438451980325806"},"_hasShrinkwrap":false},"1.3.1":{"name":"effector-forms","version":"1.3.1","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build":"cross-env NODE_ENV=production rollup -c rollup.config.js && npm run emit-dts","check-types":"tsc --noEmit","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=22.0.0 <23.0.0","effector-react":">=22.2.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^5.0.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typescript":"^4.6.3","yup":"^0.32.11"},"types":"./dist-types","gitHead":"0f27374b907659359d052ffefe6c0634d016b7bc","_id":"effector-forms@1.3.1","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-7sGciIWmp5eGEh9kDiewGBLGvArh9+5nOi64Lrw1BNV8JIi4ssMDXRgGpCZt/e/MdqvPIixJMc0cmXG9IbgAbA==","shasum":"73a4217ebe19517f497c3f74fa8beb2d5cfaddde","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.3.1.tgz","fileCount":11,"unpackedSize":220760,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDvaZ/BzLShtoQds3JbYSDi4hh2FHkdI70i/TF6Xv92iAiBhdy41lVOblkf/RJg1kjhXR6AS8KqamEPYP8xBS1YrjQ=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.3.1_1684074735535_0.7988114485911653"},"_hasShrinkwrap":false},"1.3.2":{"name":"effector-forms","version":"1.3.2","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build":"cross-env NODE_ENV=production rollup -c rollup.config.js && npm run emit-dts","check-types":"tsc --noEmit","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers","tsdoc:dev":"typedoc --watch --plugin typedoc-plugin-markdown","tsdoc:build":"typedoc --githubPages false --readme none --plugin typedoc-plugin-markdown --out docs/api src/index.ts","docs:dev":"vitepress dev docs","docs:build":"vitepress build docs","docs:preview":"vitepress preview docs"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=22.0.0 <23.0.0","effector-react":">=22.2.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^5.0.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","eslint-plugin-tsdoc":"^0.2.17","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typedoc":"^0.24.7","typedoc-plugin-markdown":"^3.15.3","typescript":"^4.6.3","vitepress":"^1.0.0-alpha.75","yup":"^0.32.11"},"types":"./dist-types","gitHead":"e3664177841ebcfb1011d56f151ce97a8bc148d3","_id":"effector-forms@1.3.2","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-FmKmuMqzYNSsEixqiRXfvB1zp5qDECh0l9+SzXtWYJKKfK2DHycSPWCVrnp5yeaGb6XlaVDQPEMMBYtrhpneYw==","shasum":"979dc822944dca1fac5454ed8f70710e81b460ca","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.3.2.tgz","fileCount":11,"unpackedSize":228191,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDQReO4O4YMEmmgsTz80ya3vjXkUYmvydecHpuAWJjdswIgRXJGAFIJBieUKq9HeV7xEvkUuY7F1zkdOT4xMhWDcGk="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.3.2_1684491119926_0.015016831821098586"},"_hasShrinkwrap":false},"1.3.3":{"name":"effector-forms","version":"1.3.3","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js","types":"./dist-types/index.d.ts"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build":"cross-env NODE_ENV=production rollup -c rollup.config.js && npm run emit-dts","check-types":"tsc --noEmit","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers","tsdoc:dev":"typedoc --watch --plugin typedoc-plugin-markdown","tsdoc:build":"typedoc --githubPages false --readme none --plugin typedoc-plugin-markdown --out docs/api src/index.ts","docs:dev":"vitepress dev docs","docs:build":"vitepress build docs","docs:preview":"vitepress preview docs"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=22.0.0 <23.0.0","effector-react":">=22.2.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^5.0.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","eslint-plugin-tsdoc":"^0.2.17","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typedoc":"^0.24.7","typedoc-plugin-markdown":"^3.15.3","typescript":"^4.6.3","vitepress":"^1.0.0-alpha.75","yup":"^0.32.11"},"types":"./dist-types","gitHead":"8384b2f9d4101fa0cc9324bbb856e6b676570458","_id":"effector-forms@1.3.3","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-cjBIPEWj90W52iW1o7ShbRJZVexO4REBdGdMCy1MtHs2yof3/6G/yXTLooC31yS0aW5E5JyGUjWL2GCClYRSiA==","shasum":"567217003caa04cb89bfa05fff09c443c260d3a1","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.3.3.tgz","fileCount":11,"unpackedSize":232412,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCv6f0DAiZTcPbsSnV3jy3UMMqiyZD1rAGuTF5hczEU6gIgKZ2y0/G7UmsiueY2XROG1rQFuqC4LmLuHzqjpM5anJQ="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.3.3_1689694182211_0.33798849126556596"},"_hasShrinkwrap":false},"1.3.4":{"name":"effector-forms","version":"1.3.4","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"types":"./dist-types/index.d.ts","import":"./dist/index.mjs","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build":"cross-env NODE_ENV=production rollup -c rollup.config.js && npm run emit-dts","check-types":"tsc --noEmit","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers","tsdoc:dev":"typedoc --watch --plugin typedoc-plugin-markdown","tsdoc:build":"typedoc --githubPages false --readme none --plugin typedoc-plugin-markdown --out docs/api src/index.ts","docs:dev":"vitepress dev docs","docs:build":"vitepress build docs","docs:preview":"vitepress preview docs"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=22.0.0 <23.0.0","effector-react":">=22.2.0 <23.0.0"},"devDependencies":{"@babel/core":"^7.16.0","@babel/plugin-transform-modules-commonjs":"^7.16.0","@babel/preset-env":"^7.16.4","@babel/preset-react":"^7.16.0","@babel/preset-typescript":"^7.16.0","@rollup/plugin-babel":"^5.3.0","@rollup/plugin-commonjs":"^21.0.1","@rollup/plugin-node-resolve":"^13.0.6","@rollup/plugin-replace":"^3.0.0","@rollup/plugin-typescript":"^8.3.0","@testing-library/react":"^12.1.2","@types/jest":"^27.0.3","@types/react":"^17.0.37","@types/yup":"^0.29.13","@typescript-eslint/eslint-plugin":"^5.6.0","@typescript-eslint/parser":"^5.6.0","babel-jest":"^27.4.4","babel-plugin-module-resolver":"^5.0.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.2.0","eslint":"^8.4.1","eslint-plugin-promise":"^4.2.1","eslint-plugin-react":"^7.20.6","eslint-plugin-tsdoc":"^0.2.17","jest":"^27.4.4","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.61.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.4.0","typedoc":"^0.24.7","typedoc-plugin-markdown":"^3.15.3","typescript":"^4.6.3","vitepress":"^1.0.0-alpha.75","yup":"^0.32.11"},"types":"./dist-types/index.d.ts","gitHead":"12ccc5248f1ec4b4eb9884292b6151f18bfe932b","_id":"effector-forms@1.3.4","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-m/Swvhf6eDyEgYINULCXLZf9uhMMicYQs4Te3bg5Hx2WdUeq5Cts18QwHU+YxoTTJh06rptWbHk0QHHmI4uPuA==","shasum":"0d32953f6f4e906be2713cd145e4f3b6ec0323a0","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-1.3.4.tgz","fileCount":13,"unpackedSize":279379,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCkhluPk32qqgQr9zgiaGhVYBXiJfOI+t0PtS0jq9USwAIhAIUXE9RVd4nSvcZfm6p5I7HcdUNTMTGZJMM0uIPjxPJA"}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_1.3.4_1689695244012_0.2869075529741181"},"_hasShrinkwrap":false},"2.0.0-next.0":{"name":"effector-forms","version":"2.0.0-next.0","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.js","module":"./dist/index.js","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.js","exports":{".":{"types":"./dist-types/index.d.ts","import":"./dist/index.mjs","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.js"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build":"cross-env NODE_ENV=production rollup -c rollup.config.js && npm run emit-dts","check-types":"tsc --noEmit","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers","tsdoc:dev":"typedoc --watch --plugin typedoc-plugin-markdown","tsdoc:build":"typedoc --githubPages false --readme none --plugin typedoc-plugin-markdown --out docs/api src/index.ts","docs:dev":"vitepress dev docs","docs:build":"vitepress build docs","docs:preview":"vitepress preview docs"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=23.0.0 <24.0.0","effector-react":">=23.0.0 <24.0.0"},"devDependencies":{"@babel/core":"^7.23.5","@babel/plugin-transform-modules-commonjs":"^7.23.3","@babel/preset-env":"^7.23.5","@babel/preset-react":"^7.23.3","@babel/preset-typescript":"^7.23.3","@rollup/plugin-babel":"^5.3.1","@rollup/plugin-commonjs":"^21.1.0","@rollup/plugin-node-resolve":"^13.3.0","@rollup/plugin-replace":"^3.1.0","@rollup/plugin-typescript":"^8.5.0","@testing-library/react":"^12.1.5","@types/jest":"^27.5.2","@types/react":"^17.0.71","@types/yup":"^0.29.14","@typescript-eslint/eslint-plugin":"^5.62.0","@typescript-eslint/parser":"^5.62.0","babel-jest":"^27.5.1","babel-plugin-module-resolver":"^5.0.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.13.0","eslint":"^8.55.0","eslint-plugin-promise":"^4.3.1","eslint-plugin-react":"^7.33.2","eslint-plugin-tsdoc":"^0.2.17","jest":"^27.5.1","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.79.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.9.1","typedoc":"^0.24.8","typedoc-plugin-markdown":"^3.17.1","typescript":"^4.9.5","vitepress":"^1.0.0-alpha.75","yup":"^0.32.11"},"types":"./dist-types/index.d.ts","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Setup](#setup)\n- [Usage](#usage)\n- [Connect to view via useUnit](#connect-to-view-via-useunit)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Set initial form](#setInitialForm)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Bulk errors](#bulk-errors)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Effector 21](#effector-21)\n\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n## Setup\n\nIf you are using **SSR**, add effector-forms to the babel plugin config\n\n```json\n{\n  \"plugins\": [\n    [\n      \"effector/babel-plugin\",\n      {\n        \"factories\": [\n          \"effector-forms\"\n        ]\n      }\n    ]\n  ],\n}\n```\n\n## Usage\n\nModel:\n```ts\nimport { restore, sample, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nsample({\n    clock: loginForm.formValidated,\n    target: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useUnit } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useUnit(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n## Connect to view via useUnit\n\nThe effector-forms entities implement the **@@unitShape** protocol! This means that instead of **useForm** and **useField** hooks, you can connect the form to the view via \"useUnit\".\n\nReact's example:\n```tsx\nimport { useUnit } from \"effector-react\"\nimport { createForm } from 'effector-forms'\n\n\nconst loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [],\n        },\n        password: {\n            init: \"\",\n            rules: [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n\nconst LoginForm = () => {\n  const email = useUnit(loginForm.fields.email)\n  const password = useUnit(loginForm.fields.password)\n  const form = useUnit(loginForm)\n\n  const submitHandler = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n  return (\n    <form onSubmit={submitHandler}>\n      <input\n          type=\"text\"\n          value={email.value}\n          onChange={(e) => email.onChange(e.target.value)}\n      />\n      <input\n          type=\"password\"\n          value={password.value}\n          onChange={(e) => password.onChange(e.target.value)}\n      />\n    </form>\n  )\n}\n```\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nsample({\n  clock: form.fields.email.onChange,\n  target: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nsample({\n    clock: form.formValidated,\n    target: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nsample({\n  clock: getUserProfileFx.doneData,\n  target: form.setForm,\n})\n```\n\n## setInitialForm\n\nIn case you get values from the backend, use the `form.setInitialForm` event instead of `form.setForm`. This event changes both the value and the initial values. The `$isDirty` flag will be false.\n\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.Schema<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nsample({\n  clock: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  target: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nsample({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Bulk errors\n\nYou can add multiple errors for many fields at once with the form.addErrors event:\n\n```ts\nsample({\n  clock: loginFx.failData.map(\n    (errors) => errors.map((err) => ({\n      field: err.field,\n      rule: \"backend\",\n      errorText: err.msg,\n    }))\n  ),\n  filter: (errors) => errors.length > 0,\n  target: loginForm.addErrors,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nsample({\n  clock: somethingHappened,\n  target: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nsample({\n  clock: somethingHappened,\n  target: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nsample({\n    clock: registerForm.formValidated,\n    target: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useUnit } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useUnit(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n## Effector 21\n\nEffector 21 is no longer supported. For Effector 21 we recommend using version **0.0.24**.\n\n```\nnpm i effector-forms@0.0.24\n```\n\nFor effector-forms v0.0.24 together with effector 21 use import\n```ts\nimport { createForm } from \"effector-forms/legacy\"\n```\n\n\nFor effector-forms < 0.0.24 with effector 21, use the normal import:\n```ts\nimport { createForm } from \"effector-forms\"\n```\n\nSSR support with effector 21 is not available. \n\n\n\n","readmeFilename":"README.md","gitHead":"42518bb9e324c16c8ac3a1cf964c5bfb667104cc","_id":"effector-forms@2.0.0-next.0","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-aFKOWBdj/kU/xedCwlmHrBzgl3OPcJ2442oZqTAcAgRiSJ4sFy5nENW7tIW+ZDosqVTVd6GkvqlMtmnSHEKroA==","shasum":"3d968964fbfca1c321dc81753aa7328f4aea465b","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-2.0.0-next.0.tgz","fileCount":13,"unpackedSize":283097,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIASx/072dTgo/emhynE4ly2KWpD7FekfI4DRDZs69TAWAiB+Y6jufLN1YmzmQ+GcF5FSbSYEotvoCH0hAsBBMPM/cg=="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_2.0.0-next.0_1702001337595_0.29191749689957924"},"_hasShrinkwrap":false},"2.0.0-next.1":{"name":"effector-forms","version":"2.0.0-next.1","description":"Effector form state factory","main":"./dist/effector-forms.cjs.js","jsnext:main":"./dist/index.mjs","module":"./dist/index.mjs","umd:main":"./dist/effector-forms.umd.js","browser":"./dist/index.mjs","exports":{".":{"types":"./dist-types/index.d.ts","import":"./dist/index.mjs","require":"./dist/effector-forms.cjs.js","node":"./dist/effector-forms.cjs.js","default":"./dist/index.mjs"}},"scripts":{"emit-dts":"dts-bundle-generator --no-check --project tsconfig.json --out-file ./dist-types/index.d.ts ./src/index.ts","build":"cross-env NODE_ENV=production rollup -c rollup.config.js && npm run emit-dts","check-types":"tsc --noEmit","test":"jest","lint":"eslint \"./**/*.{js,ts,tsx}\"","lint:fix":"eslint \"./**/*.{js,ts,tsx}\" --fix","peers":"npm-install-peers","tsdoc:dev":"typedoc --watch --plugin typedoc-plugin-markdown","tsdoc:build":"typedoc --githubPages false --readme none --plugin typedoc-plugin-markdown --out docs/api src/index.ts","docs:dev":"vitepress dev docs","docs:build":"vitepress build docs","docs:preview":"vitepress preview docs"},"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"keywords":["effector","effectorjs","form"],"author":{"name":"42px"},"license":"ISC","bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"homepage":"https://github.com/aanation/effector-forms#readme","peerDependencies":{"effector":">=23.0.0 <24.0.0","effector-react":">=23.0.0 <24.0.0"},"devDependencies":{"@babel/core":"^7.23.5","@babel/plugin-transform-modules-commonjs":"^7.23.3","@babel/preset-env":"^7.23.5","@babel/preset-react":"^7.23.3","@babel/preset-typescript":"^7.23.3","@rollup/plugin-babel":"^5.3.1","@rollup/plugin-commonjs":"^21.1.0","@rollup/plugin-node-resolve":"^13.3.0","@rollup/plugin-replace":"^3.1.0","@rollup/plugin-typescript":"^8.5.0","@testing-library/react":"^12.1.5","@types/jest":"^27.5.2","@types/react":"^17.0.71","@types/yup":"^0.29.14","@typescript-eslint/eslint-plugin":"^5.62.0","@typescript-eslint/parser":"^5.62.0","babel-jest":"^27.5.1","babel-plugin-module-resolver":"^5.0.0","cross-env":"^7.0.3","dts-bundle-generator":"^6.13.0","eslint":"^8.55.0","eslint-plugin-promise":"^4.3.1","eslint-plugin-react":"^7.33.2","eslint-plugin-tsdoc":"^0.2.17","jest":"^27.5.1","react":"^17.0.2","react-dom":"^17.0.2","react-test-renderer":"^17.0.2","rollup":"^2.79.1","rollup-plugin-terser":"^7.0.2","ts-node":"^10.9.1","typedoc":"^0.24.8","typedoc-plugin-markdown":"^3.17.1","typescript":"^4.9.5","vitepress":"^1.0.0-alpha.75","yup":"^0.32.11"},"types":"./dist-types/index.d.ts","readme":"# Effector forms\n\n- [Motivation](#motivation)\n- [Setup](#setup)\n- [Usage](#usage)\n- [Connect to view via useUnit](#connect-to-view-via-useunit)\n- [useField](#usefield)\n- [Form state](#form-state)\n- [Submit Filter](#submit-filter)\n- [Set form](#set-form)\n- [Set initial form](#setInitialForm)\n- [Validation triggers](#validation-triggers)\n- [Interdependent validations](#interdependent-validations)\n- [Usage with domain](#usage-with-domain)\n- [Rules](#rules)\n- [Use rules factory](#use-rules-factory)\n- [isDirty & isTouched](#isdirty---istouched)\n- [Show errors](#show-errors)\n- [Use external validators lib](#use-external-validators-lib)\n  * [Usage with Yup](#usage-with-yup)\n- [Add custom error manually](#add-custom-error-manually)\n- [Bulk errors](#bulk-errors)\n- [Validate by external source](#validate-by-external-source)\n- [Validate manually](#validate-manually)\n- [Reset form](#reset-form)\n- [Reset values only](#reset-values-only)\n- [Reset errors](#reset-errors)\n- [Register form (full example)](#register-form--full-example-)\n- [Typescipt users tips](#typescipt-users-tips)\n- [Advanced](#advanced)\n  * [Use external units](#use-external-units)\n- [Effector 21](#effector-21)\n\n\n## Motivation\n\nIf you working with forms in an Effector-application, there is often a lot of boilerplate code, such as:\n\n```js\nexport const emailChanged = login.event()\nexport const passwordChanged = login.event()\n\nexport const submitForm = login.event()\n\nexport const $email = login.store('')\nexport const $password = login.store('')\n\nexport const $form = combine({\n  password: $password,\n  email: $email,\n})\n\n$email.on(emailChanged, (_, email) => email)\n$password.on(passwordChanged, (_, password) => password)\n```\n\nIf you also need validation, it really hurts. This library was created to improve the \"form experience\" in an effector application by generating form state from declarative configuration. \n\nThe library comes with hooks for react/react-native, however you can use it with VueJS or Forest as well (in the case of VueJS, you have to connect it to the view layer yourself). \n\n**Good typescript support!** :heart: :v: :+1:\n\n## Setup\n\nIf you are using **SSR**, add effector-forms to the babel plugin config\n\n```json\n{\n  \"plugins\": [\n    [\n      \"effector/babel-plugin\",\n      {\n        \"factories\": [\n          \"effector-forms\"\n        ]\n      }\n    ]\n  ],\n}\n```\n\n## Usage\n\nModel:\n```ts\nimport { restore, sample, createEffect } from \"effector\"\nimport { createForm } from 'effector-forms'\n\nexport const loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\", // field's store initial value\n            rules: [\n                {\n                    name: \"email\",\n                    validator: (value: string) => /\\S+@\\S+\\.\\S+/.test(value)\n                },\n            ],\n        },\n        password: {\n            init: \"\", // field's store initial value\n            rules: [\n              {\n                name: \"required\",\n                validator: (value: string) => Boolean(value),\n              }\n            ],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\nexport const loginFx = createEffect()\n\nsample({\n    clock: loginForm.formValidated,\n    target: loginFx,\n})\n```\n\nThe **createForm** factory creates stores and events of the form and binds them by declarative configuration. **formValidated** effector event will be triggered when the form is submitted (if all its fields are valid). Option **validateOn** sets an array of triggers by which the form values ​​will be validated. Possible conditions: submit, blur, change. The value of each field and validation errors are stored in effector stores.\n\nAfter we have created the form, we can connect it to the view using the **useForm** hook:\n\n```tsx\nimport { useForm } from 'effector-forms'\nimport { useUnit } from 'effector-react'\nimport { loginForm, loginFx } from '../model'\n\nexport const LoginForm = () => {\n  const { fields, submit, eachValid } = useForm(loginForm)\n  const pending = useUnit(loginFx.pending)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit)}>\n        <input\n            type=\"text\"\n            value={fields.email.value}\n            disabled={pending}\n            onChange={(e) => fields.email.onChange(e.target.value)}\n        />\n        <div>\n            {fields.email.errorText({\n                \"email\": \"you must enter a valid email address\",\n            })}\n        </div>\n        <input\n            type=\"password\"\n            value={fields.password.value}\n            disabled={pending}\n            onChange={(e) => fields.password.onChange(e.target.value)}\n        />\n        <div>\n            {fields.password.errorText({\n                \"required\": \"password required\"\n            })}\n        </div>\n        <button\n          disabled={!eachValid || pending}\n          type=\"submit\"\n        >\n          Login\n        </button>\n    </form>\n  )\n}\n```\n\n## Connect to view via useUnit\n\nThe effector-forms entities implement the **@@unitShape** protocol! This means that instead of **useForm** and **useField** hooks, you can connect the form to the view via \"useUnit\".\n\nReact's example:\n```tsx\nimport { useUnit } from \"effector-react\"\nimport { createForm } from 'effector-forms'\n\n\nconst loginForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [],\n        },\n        password: {\n            init: \"\",\n            rules: [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n\nconst LoginForm = () => {\n  const email = useUnit(loginForm.fields.email)\n  const password = useUnit(loginForm.fields.password)\n  const form = useUnit(loginForm)\n\n  const submitHandler = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n  return (\n    <form onSubmit={submitHandler}>\n      <input\n          type=\"text\"\n          value={email.value}\n          onChange={(e) => email.onChange(e.target.value)}\n      />\n      <input\n          type=\"password\"\n          value={password.value}\n          onChange={(e) => password.onChange(e.target.value)}\n      />\n    </form>\n  )\n}\n```\n\n## useField\n\n```tsx\nimport { createForm, useField } from 'effector-forms'\n\nexport const form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nconst Email = () => {\n  const { value, onChange } = useField(form.fields.email)\n\n  return (\n    <input\n      type=\"text\"\n      placeholder=\"email\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Password = () => {\n  const { value, onChange } = useField(form.fields.password)\n  \n  return (\n    <input\n      type=\"password\"\n      placeholder=\"password\"\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n    />\n  )\n}\n\nconst Form = () => {\n  const onSubmit = (e) => {\n    e.preventDefault()\n    form.submit()\n  }\n\n\n  <form onSubmit={onSubmit}>\n    <Email />\n    <Password />\n    <button type=\"submit\">Login</button>\n  </form>\n}\n```\n\n## Form state\n\nYou can access the state of fields, form and its events directly in the model:\n\n```ts\nconst form = createForm(formConfig)\n\nform.fields.email.$value.watch(() => {\n  console.log(\"username field changed\")\n})\n\nform.fields.email.$errors.watch((errors) => {\n  console.log(errors)\n})\n\nform.$values.watch(() => {\n  console.log(\"form changed\")\n})\n\nform.$eachValid.watch((eachValid) => {\n  if (eachValid) {\n    console.log(\"form valid\")\n  }\n})\n\nform.fields.email.onChange(\"value\")\n\nsample({\n  clock: form.fields.email.onChange,\n  target: someEvent,\n})\n```\n\n## Submit Filter\n\nSometimes we need to prevent form submission conditionally, for example if we have an error from the server. \nTo do this, you can use the **filter** option by passing a boolean Store:\n\n```ts\nexport const loginFx = createEffect()\nconst $serverError = restore(loginFx.failData, null)\n\n\nexport const loginForm = createForm({\n    filter: $serverError.map((error) => error === null),\n    fields,\n    validateOn: [\"submit\"],\n})\n\n$serverError.reset(form.$values.updates)\n\nsample({\n    clock: form.formValidated,\n    target: loginFx,\n})\n```\n\n\nIn this example the **formValidated** event will be triggered after submit only if the **$serverError** is null & form is valid.\n\n\n## Set form\n\nIt is often necessary to set the value of the form by an event. This is useful for initializing initial field values. In this case you can use **setForm** event:\n\n\n```ts\ntype User = {\n  username: string\n  about?: string\n  birhDate?: string\n}\n\nconst getUserProfileFx = createEffect<void, User, Error>()\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n    },\n    about: {\n      init: \"\" as string,\n    },\n    birthDate: {\n      init: null as string | null,\n    },\n  },\n})\n\nsample({\n  clock: getUserProfileFx.doneData,\n  target: form.setForm,\n})\n```\n\n## setInitialForm\n\nIn case you get values from the backend, use the `form.setInitialForm` event instead of `form.setForm`. This event changes both the value and the initial values. The `$isDirty` flag will be false.\n\n\n## Validation triggers\n\nYou can validate different form fields against different triggers:\n\n```tsx\nconst form = createForm({\n  validateOn: [\"submit\"],\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRulesArr,\n      validateOn: [\"blur\"],\n    },\n    username: {\n      init: \"\",\n      rules: usernameRulesArr,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRulesArr,\n      validateOn: [\"change\"],\n    },\n  },\n})\n\nconst RegisterForm = () => {\n  const { submit, fields } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <button type=\"submit\">Register</button>\n    </form>\n  )\n}\n```\n\n\nIn this example:\n* the email field will be validated on blur and submit\n* the password field will be validated on change and submit\n* the username field will be validated on submit only\n\nNote that you must directly trigger the onBlur event on the email field. This is due to the fact that the form state is separated from the view, and the form state \"knows nothing\" about input field event.\n\n\nYou can also pass multiple triggers:\n```ts\nconst form = createForm({\n  validateOn: [\"submit\", \"blur\"],\n  fields: formFields,\n})\n```\n\n\n## Interdependent validations\n\nIt so happens that to validate a field, you need to know the value of another field. In this case you can use the second argument of the validator function:\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n    confirmation: {\n      init: \"\",\n      rules: [\n        {\n          name: \"confirmation\",\n          validator: (confirmation, { password }) => confirmation === password\n        },\n      ],\n    },\n  }\n})\n\n```\n\n\n## Usage with domain\n\n\nIf you need to have all units of a form (fields stores and events) in a domain, you can pass the domain option. This can be useful for SSR.\n\n```ts\nconst myDomain = createDomain()\nconst form = createForm({\n  domain: myDomain,\n  fields: formFields,\n})\n```\n\n## Rules\n\nValidation rule is an object with name and validator properties:\n\n```ts\n{\n  name: \"required\",\n  validator: (value) => Boolean(value),\n}\n```\n\nTo reuse validation rules, put them in a separate module. Very often the validator needs a parameter. For this reason, we recommend using \"factory-style\" when creating your validators library:\n\n```ts\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n    maxLength: (max: number): Rule<string> => ({\n        name: \"maxLength\",\n        validator: (value) => value.length <= max \n    }),\n}\n```\n\n```ts\nimport { rules } from 'my-validation-rules'\n\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        rules.email(),\n      ],\n    },\n    password: {\n      init: \"\",\n      rules: [\n        rules.required(),\n        rules.minLength(3),\n      ],\n    },\n  },\n})\n```\n\n## Use rules factory\n\nSometimes you need to calculate validation rules based on the current state of the form.\nIn this case, you can pass a factory function to rules:\n\n```ts\nconst form = createForm({\n    fields: {\n        needNotification: {\n            init: false,\n            rules: [],\n        },\n        email: {\n            init: \"\" as string,\n            rules: (value: string, form) => form.needNotification ? [email()] : [],\n        },\n    },\n    validateOn: [\"submit\"],\n})\n```\n\n\n## isDirty & isTouched\n\nEach field has two boolean stores: $isTouched and $isDirty.\n\n*$isTouched* true if this field has ever changed (the onChange event has been called at least once). false otherwise.\n\n*isDirty* true if the current value is different from the initial (===). false otherwise.\n\nBoth fields are reset on form.reset event.\n\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"email@example.com\",\n      rules: [\n        rules.required(),\n      ],\n    },\n  }\n})\n\nform.fields.email.onChange(\"\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // true\n\nform.fields.email.onChange(\"email@example.com\")\nconsole.log(form.fields.email.$isTouched.getState()) // true\nconsole.log(form.fields.email.$isDirty.getState()) // false\n\nform.reset()\nconsole.log(form.fields.email.$isTouched.getState()) // false\nconsole.log(form.fields.email.$isDirty.getState()) // false\n```\n\n[Sandbox](https://codesandbox.io/s/sad-wildflower-0hjc9?file=/src/App.js)\n\n## Show errors\n\nYou can use two different approaches to output error text\n\n1. Define the error text in the validator function\n2. Define error text in the view layer (components)\n\n\nIn the first approach returns an object instead of a boolean from the validator function:\n\n```ts\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => ({\n            isValid: Boolean(value),\n            errorText: \"Required field\",\n        }),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n```\n\nThe error text can be displayed using the errorText helper:\n\n```tsx\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\")}\n      </div>\n    </form>\n  )\n}\n```\n\n\nAlternatively, you can define the error text by passing the second argument to the errorText helper:\n\n```tsx\nconst rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n}\n\n\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        rules.required(),\n      ],\n    }\n  }\n})\n\nconst Form = () => {\n  const { fields, hasError, errorText } = useForm(form)\n\n  return (\n    <form>\n      <input\n        type=\"text\"\n        placeholder=\"username\"\n        className={hasError(\"username\") ? \"invalid\" : \"\"}\n        value={fields.username.value}\n        onChange={(e) => fields.username.onChange(e.target.value)}\n      />\n      <div class=\"error-text\">\n        {errorText(\"username\", {\n          \"required\": \"username field is required\"\n        })}\n      </div>\n    </form>\n  )\n}\n```\n\n\nYou can also combine both approaches by overriding errors of only certain rules in the second argument errorText.\n\n## Use external validators lib\n\nYou can implement your own validation rules or wrap an external rules library, for example [validator.js](https://www.npmjs.com/package/validator)\n\n```ts\nimport { Rule } from 'effector-forms'\nimport isEmail from 'validator/lib/isEmail'\n\nfunction createRule<Value>(\n  name: string,\n  validator: (v: any) => boolean\n): Rule<Value> => ({\n  name,\n  validator\n})\n\nexport const rules = {\n  email: = () => createRule(\"email\", isEmail)\n}\n```\n\n### Usage with Yup\nyup validation wrapper:\n```ts\nimport * as yup from 'yup'\nimport { Rule } from \"effector-forms\";\n\nexport function createRule<V, T = any>({\n    schema,\n    name,\n}: { schema: yup.SchemaOf<T>, name: string }): Rule<V> {\n    return {\n        name,\n        validator: (v: V) => {\n            try {\n                schema.validateSync(v)\n                return {\n                    isValid: true,\n                    value: v,\n                }\n            } catch (err) {\n                return {\n                    isValid: false,\n                    value: v,\n                    errorText: err.message,\n                }\n            }\n        },\n    }\n}\n```\n\nuse it with your form:\n```ts\nimport * as yup from 'yup'\nimport { createForm } from 'effector-forms'\nimport { createRule } from '@/lib/create-yup-rule'\n\n\nconst form = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: 'email',\n                    schema: yup.string().email().required(),\n                })\n            ],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                createRule<string>({\n                    name: \"password\",\n                    schema: yup.string().required().min(3),\n                })\n            ],\n        },\n    },\n})\n\n```\n\n## Add custom error manually\n\nSometimes you need to add an error manually. To do this, you can use the addError event on the field:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nsample({\n  clock: somethingHappened.map(() => ({\n    rule: \"my-custom\",\n    errorText: \"somethingHappened\",\n  })),\n  target: form.fields.addError,\n})\n```\n\nyou can use this to add a server error to the field:\n\n```ts\nconst loginFx = createEffect<{ email: string, password: string }, void, Error>()\n\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n    },\n    password: {\n      init: \"\",\n    },\n  },\n})\n\nsample({\n  source: loginFx.failData.map(\n    (error) => error.name === \"already-exists\" ? { rule: \"already-exists\" } : null\n  ),\n  filter: Boolean,\n  target: loginForm.fields.email.addError,\n})\n```\n\n## Bulk errors\n\nYou can add multiple errors for many fields at once with the form.addErrors event:\n\n```ts\nsample({\n  clock: loginFx.failData.map(\n    (errors) => errors.map((err) => ({\n      field: err.field,\n      rule: \"backend\",\n      errorText: err.msg,\n    }))\n  ),\n  filter: (errors) => errors.length > 0,\n  target: loginForm.addErrors,\n})\n```\n\n## Validate by external source\n\nYou can pass external store to the validation rule. This storage will be available in the validator function:\n\n```ts\nconst loginForm = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required_if\",\n          source: $needToValidate,\n          validator: (value, form, needToValidate) => {\n            if (!needToValidate) return true\n            return Boolean(value)\n          }\n        },\n      ],\n    },\n  },\n})\n```\n\n\n## Validate manually\n\nUse the **validate** event to manually validate the field at any time:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n    },\n  },\n})\n\nconst somethingHappened = createEvent()\n\nsample({\n  clock: somethingHappened,\n  target: form.fields.username.validate,\n})\n```\n\nValidate all fields:\n\n```ts\nsample({\n  clock: somethingHappened,\n  target: form.validate,\n})\n```\n\n## Reset form\nYou can reset some field value or all fields:\n```ts\nconst form = createForm(formConfig)\n\n// reset specific field\nform.fields.email.reset()\n\n// reset form\nform.reset()\n```\n\n\nform.reset event resets both values ​​and errors\n\n## Reset values only\n```ts\nconst form = createForm(formConfig)\nform.resetValues()\n```\n\n## Reset errors\n\nYou can manually reset any field validation errors:\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.submit()\nform.fields.username.resetErrors()\n```\n\nReset forms errors (all fields):\n```ts\nconst form = createForm({\n  fields: {\n    email: {\n      init: \"\",\n      rules: emailRules,\n    },\n    password: {\n      init: \"\",\n      rules: passwordRules,\n    },\n  },\n})\n\nform.fields.username.onChange(\"invalid email\")\nform.fields.username.onChange(\"invalid password\")\nform.resetErrors() // clear all errors\n```\n\n\n## Register form (full example)\n\nRules:\n```ts\nimport { Rule } from 'effector-forms'\n\nexport const rules = {\n    required: (): Rule<string> => ({\n        name: \"required\",\n        validator: (value) => Boolean(value),\n    }),\n    email: (): Rule<string> => ({\n        name: \"email\",\n        validator: (value) => /\\S+@\\S+\\.\\S+/.test(value)\n    }),\n    minLength: (min: number): Rule<string> => ({\n        name: \"minLength\",\n        validator: (value) => value.length >= min \n    }),\n}\n```\n\n\nModel:\n```ts\nimport { restore, createEffect } from 'effector'\nimport { createForm } from 'effector-forms'\nimport { rules } from '@/validation-rules'\n\n\nexport const registerFx = createEffect<{ email: string, password: string }, void, Error>()\nconst $registerError = restore(registerFx.failData, null)\n\nexport const registerForm = createForm({\n    fields: {\n        email: {\n            init: \"\",\n            rules: [\n                rules.email(),\n            ],\n            validateOn: [\"blur\"],\n        },\n        password: {\n            init: \"\",\n            rules: [\n                rules.required(),\n                rules.minLength(3),\n            ],\n        },\n        confirm: {\n            init: \"\",\n            rules: [\n                {\n                    name: \"passwords-equal\",\n                    validator: (value: string, { password }) => {\n                        return value === password\n                    },\n                },\n            ],\n            validateOn: [\"change\"],           \n        },\n    },\n    validateOn: [\"submit\"],\n})\n\n$registerError.reset(registerForm.$values.updates)\n\nsample({\n    clock: registerForm.formValidated,\n    target: registerFx,\n})\n```\n\nView:\n```tsx\nimport { useUnit } from 'effector'\nimport { useForm } from 'effector-forms'\nimport { registerForm, registerFx } from '../model'\n\nconst RegisterForm = () => {\n  const pending = useUnit(registerFx.pending)\n  const { submit, fields, eachValid } = useForm(form)\n\n  const onSubmit = (e) => {\n    e.preventDefault()\n    submit()\n  }\n\n  return (\n    <form onSubmit={onSubmit}>\n      <input\n        type=\"text\"\n        placeholder=\"email\"\n        value={fields.email.value}\n        onBlur={() => fields.email.onBlur()}\n        disabled={pending}\n        onChange={(e) => fields.email.onChange(e.target.value)}\n      />\n      <input\n        type=\"password\"\n        placeholder=\"password\"\n        value={fields.password.value}\n        disabled={pending}\n        onChange={(e) => fields.password.onChange(e.target.value)}\n      />\n      <input\n        type=\"text\"\n        placeholder=\"confirm\"\n        value={fields.confirm.value}\n        disabled={pending}\n        onChange={(e) => fields.confirm.onChange(e.target.value)}\n      />\n      <button type=\"submit\" disabled={pending || !eachValid}>\n        Register\n      </button>\n    </form>\n  )\n}\n```\n\n## Typescipt users tips\n\n\nTypes for stores and events generated by the createForm factory are inferred by the passed form configuration. Type inference is based on \"init\" field type and validator type. If you do not specify the type of the argument in the validator, the type of the resulting field may not be desired. For example:\n\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value\n```\n\nthe type of $usernameVal is ```Store <\"\">```, not ```Store<string>```. This happens because the empty string is inferred as a \"string literal\", not a \"string\".\n\nTo avoid such problems, cast init to the desired type or define the type of validator first argument:\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\" as string,\n      rules: [\n        {\n          name: \"required\",\n          validator: (value) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n```ts\nconst form = createForm({\n  fields: {\n    username: {\n      init: \"\",\n      rules: [\n        {\n          name: \"required\",\n          validator: (value: string) => Boolean(value),\n        },\n      ],\n    }\n  },\n})\n\nconst $usernameVal = form.fields.username.$value // Store<string>\n```\n\n## Advanced\n\n### Use external units\n\nBy default, **createForm** factory creates all form units (stores & events). Sometimes there is a need to pass externally created form units. In this case, **createForm** just binds them and creates combine units:\n\n```ts\nimport { createForm, ValidationError } from 'effector-forms'\n\nconst form = createForm({\n  fields: {\n    email: {\n        init: \"\",\n        rules: [],\n        units: {\n          // all units are optional\n          $value: createStore(\"\"),\n          $errors: createStore<ValidationError<string>[]>([]),\n          $isTouched: createStore<boolean>(false),\n          onChange: createEvent<string>(),\n          changed: createEvent<string>(),\n          onBlur: createEvent<void>(),\n          addError: createEvent<{ rule: string; errorText?: string }>(),\n          validate: createEvent<void>(),\n          reset: createEvent<void>(),\n          resetErrors: createEvent<void>(),\n        },\n    },\n  },\n  units: {\n    // all units are optional\n    submit: createEvent(),\n    reset: createEvent(),\n    resetTouched: createEvent(),\n    formValidated: createEvent<{ email: string }>(),\n    setForm: createEvent<{ email?: string }>(),\n  },\n  validateOn: [\"submit\"],\n})\n```\n\n## Effector 21\n\nEffector 21 is no longer supported. For Effector 21 we recommend using version **0.0.24**.\n\n```\nnpm i effector-forms@0.0.24\n```\n\nFor effector-forms v0.0.24 together with effector 21 use import\n```ts\nimport { createForm } from \"effector-forms/legacy\"\n```\n\n\nFor effector-forms < 0.0.24 with effector 21, use the normal import:\n```ts\nimport { createForm } from \"effector-forms\"\n```\n\nSSR support with effector 21 is not available. \n\n\n\n","readmeFilename":"README.md","gitHead":"237ead06a4eb7bcc5ccbc37beda3e0ff0f0d0ce1","_id":"effector-forms@2.0.0-next.1","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-F5YNYOTIiF6NVMCdAlA1206NtjEivEoB6G8LPCPpxCukBJQfOvna0CUahL4tX+Czz8d6KlLWh5HZV+i2Aj+zJA==","shasum":"ab609c58b9e9a824b685a4d24d23e4798655e213","tarball":"https://registry.npmjs.org/effector-forms/-/effector-forms-2.0.0-next.1.tgz","fileCount":11,"unpackedSize":236953,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAgbuZvawS4dGC04gWn8FZ5BD07A79qf4sH74KI8jkn6AiEAl82oRE5OyRonhNtCwpnMZJhs2ahdEzS5mBFpYLDCcu0="}]},"_npmUser":{"name":"42px","email":"office@42px.ru"},"directories":{},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/effector-forms_2.0.0-next.1_1704744022003_0.23179266962879352"},"_hasShrinkwrap":false}},"time":{"created":"2020-08-15T11:56:40.595Z","0.0.1":"2020-08-15T11:56:40.693Z","modified":"2024-01-08T20:00:22.371Z","0.0.2":"2020-08-15T12:01:17.123Z","0.0.5":"2020-08-17T13:17:03.068Z","0.0.6":"2020-08-24T18:57:05.683Z","0.0.7":"2020-08-24T18:57:40.776Z","0.0.8":"2020-08-29T06:06:21.824Z","0.0.9":"2020-08-29T06:17:59.448Z","0.0.10":"2020-08-31T13:22:28.644Z","0.0.11":"2020-09-21T15:45:13.675Z","0.0.12":"2020-09-21T15:58:39.816Z","0.0.13":"2020-09-22T12:39:30.930Z","0.0.14":"2020-09-28T12:08:57.460Z","0.0.15":"2020-10-08T08:50:02.099Z","0.0.16":"2020-10-08T09:23:37.592Z","0.0.17":"2020-10-08T10:07:43.045Z","0.0.18":"2020-10-08T10:36:55.990Z","0.0.19":"2021-01-11T14:03:45.434Z","0.0.20":"2021-01-28T11:52:32.770Z","0.0.21":"2021-04-07T11:00:46.454Z","0.0.22":"2021-10-20T19:59:35.127Z","0.0.23-alpha.0":"2021-12-12T22:01:59.645Z","0.0.23-alpha.1":"2021-12-12T22:30:10.928Z","0.0.23-alpha.2":"2021-12-12T22:38:00.307Z","0.0.23-alpha.3":"2021-12-12T22:44:49.053Z","0.0.23-alpha.4":"2021-12-12T22:45:41.802Z","0.0.23-alpha.5":"2021-12-12T22:50:21.129Z","0.0.23-alpha.6":"2021-12-12T23:02:46.987Z","0.0.23-alpha.7":"2021-12-12T23:04:33.403Z","0.0.23-alpha.8":"2022-01-25T09:39:51.648Z","0.0.23-alpha.9":"2022-04-17T21:04:57.311Z","0.0.23-alpha.10":"2022-04-17T21:14:02.129Z","0.0.23-beta.1":"2022-04-17T22:58:40.270Z","0.0.23-beta.2":"2022-04-17T23:16:33.919Z","0.0.23-beta.3":"2022-04-17T23:33:23.077Z","0.0.23-beta.4":"2022-04-17T23:41:18.059Z","0.0.24":"2022-05-17T14:49:17.780Z","0.0.25-beta.1":"2023-05-13T11:02:36.189Z","1.1.0-beta":"2023-05-13T16:00:13.352Z","1.1.0":"2023-05-13T16:09:08.570Z","1.2.0":"2023-05-13T20:42:17.531Z","1.2.1":"2023-05-13T21:20:24.212Z","1.2.2":"2023-05-13T21:34:47.838Z","1.2.3":"2023-05-13T21:50:35.351Z","1.2.4":"2023-05-14T08:48:40.348Z","1.3.0":"2023-05-14T14:16:32.207Z","1.3.1":"2023-05-14T14:32:15.769Z","1.3.2":"2023-05-19T10:12:00.190Z","1.3.3":"2023-07-18T15:29:42.390Z","1.3.4":"2023-07-18T15:47:24.207Z","2.0.0-next.0":"2023-12-08T02:08:57.778Z","2.0.0-next.1":"2024-01-08T20:00:22.215Z"},"maintainers":[{"name":"42px","email":"office@42px.ru"}],"description":"Effector form state factory","homepage":"https://github.com/aanation/effector-forms#readme","keywords":["effector","effectorjs","form"],"repository":{"type":"git","url":"git+https://github.com/aanation/effector-forms.git"},"author":{"name":"42px"},"bugs":{"url":"https://github.com/aanation/effector-forms/issues"},"license":"ISC","readme":"","readmeFilename":""}