├── .babelrc
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── CNAME
├── LICENSE
├── README.md
├── _config.yml
├── docs
├── API.md
├── FAQ.md
└── Recipes.md
├── example
├── .babelrc
├── Components
│ ├── AdvancedDeepNestedData.js
│ ├── AdvancedInstantValidation.js
│ ├── AdvancedNoInstantValidation.js
│ ├── AsyncForm.js
│ ├── Basic.js
│ ├── BasicWithIsValid.js
│ ├── BasicWithoutSubmitButton.js
│ ├── Form.js
│ ├── FormWithoutSubmitButton.js
│ ├── ServerSideErrors.js
│ ├── SetAsyncErrorsExample.js
│ ├── SimpleForm.js
│ └── createErrorMessage.js
├── app.js
├── demo
│ ├── bundle.js
│ ├── bundle.js.map
│ └── index.html
├── helpers.js
├── index.html
├── index.template.html
├── package.json
├── validationRules.js
├── webpack.config.demo.js
├── webpack.config.js
└── yarn.lock
├── package.json
├── rollup.config.js
├── src
├── Revalidation.js
├── constants.js
├── helpers
│ ├── debounce.js
│ └── getValue.js
├── index.js
├── updaters
│ ├── index.js
│ ├── types.js
│ ├── updateFormValues.js
│ └── updateSyncErrors.js
├── utils
│ ├── index.js
│ └── isValid.js
└── validate.js
├── test
├── revalidation.test.js
├── updaters
│ ├── updateFormValues.test.js
│ └── updateSyncErrors.test.js
└── utils
│ └── isValid.test.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"],
3 | "plugins": ["transform-flow-strip-types", "transform-object-rest-spread", "ramda"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "parser": "babel-eslint",
4 | "parserOptions": {
5 | "ecmaVersion": 7,
6 | "sourceType": "module",
7 | "ecmaFeatures": {
8 | "impliedStrict": true,
9 | "experimentalObjectRestSpread": true
10 | }
11 | },
12 | "rules": {
13 | "arrow-parens": 0,
14 | "class-methods-use-this": 0,
15 | "import/no-extraneous-dependencies": 0,
16 | "no-confusing-arrow": 0,
17 | "no-duplicate-imports": 0,
18 | "no-else-return": 0,
19 | "no-prototype-builtins": 0,
20 | "no-unused-vars": [2, { "varsIgnorePattern": "^_+$" }],
21 | "quote-props": 0,
22 | "semi": [2, "never"],
23 | "space-before-function-paren": 0,
24 | "symbol-description": 0,
25 | "valid-jsdoc": 0,
26 | "max-len": 0
27 | }
28 | }
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/node_modules/.*
3 |
4 | [include]
5 |
6 | [libs]
7 |
8 | [options]
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | dist
4 | npm-debug.log
5 | yarn-error.log
6 | example/node_modules
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | examples
3 | test
4 | .babelrc
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache: yarn
3 | node_js:
4 | - "6"
5 | sudo: false
6 | script:
7 | - npm test
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | revalidation.oss.25th-floor.com
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 25th-floor GmbH
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Revalidation
2 |
3 | ### Higher Order Component for Forms in React
4 |
5 |
6 | __Revalidation__ lets you write your forms as __stateless function components__, taking care of managing the local form state
7 | as well as the validation. Revalidation also works with classes and will support other React-like libraries like __Preact__ or __Inferno__
8 | in the future.
9 |
10 | ### Use Case
11 | Form handling sounds trivial sometimes, but let’s just take a second to think about what is involved in the process.
12 | We need to define form fields, we need to validate the fields,
13 | we also might need to display errors according to the fact if the input validates,
14 | furthermore we need to figure out if the validation is instant or only after clicking
15 | on a submit button and so on and so forth.
16 |
17 | ### Why Revalidation?
18 | There are a number of solutions already available in __React__ land, each with there own approach on how to tackle aforementioned problems.
19 | __Revalidation__ is another approach on taming the problems that come with forms by only doing two things: managing the
20 | local form component state and validating against a defined set of rules. There is no real configuration and very
21 | little is hidden away from user land. This approach has pros and cons obviously. The benefit we gain, but declaring an initial state
22 | and a set of rules is that we can reuse parts of the spec and compose those specs to bigger specs. The downside is that
23 | Revalidation doesn't abstract away the form handling itself. The only configurations available are `validateSingle` and
24 | `validateOnChange`, while the first enables to define if the predicates functions are against all fields or only that one updated,
25 | the latter enables to turn dynamic validation on and off all together. This is it. Everything is up to the form implementer.
26 |
27 | __Revalidation__ enhances the wrapped Component by passing a `revalidation` prop containing a number of properties and functions
28 | to manage the state. There are no automatic field updates, validations or _onsubmit_ actions, Revalidation doesn't know how
29 | the form is implemented or how it should handle user interactions.
30 |
31 | Let's see an example to get a better idea on how this could work.
32 | For example we would like to define a number of validation rules for two inputs, _name_ and _random_.
33 | More often that not, inside an `onChange(name, value)` f.e, we might start to hard code some rules and verify them against
34 | the provided input:
35 |
36 | ```js
37 |
38 | onChange(name, value) {
39 | if (name === 'lastName') {
40 | if (hasCapitalLetter(lastName)) {
41 | // then do something
42 | }
43 | }
44 | // etc...
45 | }
46 |
47 | ```
48 |
49 | This example might be exaggerated but you get the idea.
50 | Revalidation takes care of running your predicate functions against defined field inputs, enabling to decouple the actual input from the predicates.
51 |
52 |
53 | ```javascript
54 | const validationRules = {
55 | name: [
56 | [ isGreaterThan(5),
57 | `Minimum Name length of 6 is required.`
58 | ],
59 | ],
60 | random: [
61 | [ isGreaterThan(7), 'Minimum Random length of 8 is required.' ],
62 | [ hasCapitalLetter, 'Random should contain at least one uppercase letter.' ],
63 | ]
64 | }
65 | ```
66 | And imagine this is our input data.
67 |
68 | ```javascript
69 | const inputData = { name: 'abcdef', random: 'z'}
70 | ```
71 |
72 | We would like to have a result that displays any possible errors.
73 |
74 | Calling validate `validate({inputData, validationRules)`
75 | should return
76 | ```javascript
77 | {name: true,
78 | random: [
79 | 'Minimum Random length of 8 is required.',
80 | 'Random should contain at least one uppercase letter.'
81 | ]}
82 | ```
83 |
84 | Revalidate does exactly that, by defining an initial state and the validation rules it takes care of updating and validating
85 | any React Form Component. Revalidate also doesn't know how your form is built or if it is even a form for that matter.
86 | This also means, a form library can be built on top Revalidation, making it a sort of meta form library.
87 |
88 |
89 | ### Getting started
90 |
91 | Install revalidation via npm or yarn.
92 |
93 |
94 | ```
95 | npm install --save revalidation
96 | ```
97 |
98 | ### Example
99 |
100 | We might have a stateless function component that receives a prop ___form___, which include the needed field values.
101 |
102 | ```javascript
103 |
104 | import React, {Component} from 'react'
105 |
106 | const Form = ({ form, onSubmit }) =>
107 | (
108 |
109 |
110 |
111 |
115 |
116 |
117 |
118 |
122 |
123 |
124 |
125 | )
126 |
127 | ```
128 |
129 | Next we might have a defined set of rules that we need to validate for given input values.
130 |
131 | ```javascript
132 | const validationRules = {
133 | name: [
134 | [isNotEmpty, 'Name should not be empty.']
135 | ],
136 | random: [
137 | [isLengthGreaterThan(7), 'Minimum Random length of 8 is required.'],
138 | [hasCapitalLetter, 'Random should contain at least one uppercase letter.'],
139 | ]
140 | }
141 |
142 | ```
143 |
144 | Further more we know about the inital form state, which could be empty field values.
145 |
146 | ```javascript
147 | const initialState = {password: '', random: ''}
148 | ```
149 |
150 | Now that we have everything in place, we import Revalidation.
151 |
152 | ```js
153 | import Revalidation from 'revalidation'
154 | ```
155 | Revalidation only needs the Component and returns a Higher Order Component accepting the following props:
156 |
157 | - __`initialState`__ *(Object)*
158 |
159 | - __`rules`__ *(Object)*
160 |
161 | - __`validateSingle`__ *(Boolean)*
162 |
163 | - __`validateOnChange`__: *(Boolean|Function)*
164 |
165 | - __`asyncErrors`__ *(Object)*
166 |
167 | - __`updateForm`__ *(Object)*
168 |
169 |
170 | ```js
171 |
172 | const enhancedForm = revalidation(Form)
173 |
174 | // inside render f.e.
175 |
176 | submitted}
185 | */}
186 | />
187 |
188 | ```
189 |
190 | This enables us to rewrite our Form component, which accepts a ___revalidation___ prop now.
191 |
192 | ```js
193 |
194 | const createErrorMessage = (errorMsgs) =>
195 | isValid(errorMsgs) ? null :
224 | )
225 |
226 | export default revalidation(Form)
227 | ```
228 |
229 | revalidtion returns an object containing:
230 | - __form__: form values
231 | - __onChange__: a function expecting form name and value, additionally one can specify if the value and/or the validation should be updated and also accepts a callback function that will be run after an update has occurred. i.e.
232 |
233 | ```js
234 | onChange('name', 'foo')
235 | // or
236 | onChange('name', 'foo', [UPDATE_FIELD])
237 | // or
238 | onChange('name', 'foo', null, ({valid, form}) => valid ? submitCb(form) : null )
239 | ```
240 |
241 | - __updateState__: a function expecting all the form values, f.e. Useful when wanting to reset the form. Depending on the setting either a validation will occur or not.
242 |
243 |
244 | ```js
245 |
246 | ```
247 |
248 | - __valid__: calculated validation state, f.e. initially disabling the submit button when a form is rendered.
249 | - __submitted__: set to true once the form has been submitted.
250 | - __errors__: the errors object containing an array for every form field.
251 | - __onSubmit__: validates all fields at once, also accepts a callback function that will be called after the a validation state has been calculated. The callback function receives the current state including the valid state.
252 |
253 | ```js
254 |
259 | ```
260 |
261 | - __updateErrors__: Enables to update any errors.
262 |
263 |
264 | - __updateAsyncErrors__: Enables to update any asynchronous errors. Useful when working with asynchronous validations.
265 | Pass the `updateAsyncErrors` to a callback, once the validation is finished set the result manually.
266 |
267 | ```js
268 |
271 |
272 | // use in another Component...
273 | class HigherUpComponent extends React.Component {
274 | onSubmit = (formValues, updateAsyncErrors) => {
275 | setTimeout(() => {
276 | // something went wrong...
277 | updateAsyncErrors({ name: ['Username is not available'] })
278 | }, 1000)
279 | }
280 |
281 | render() {
282 | {/* ... */}
283 | }
284 | }
285 | ```
286 |
287 | - __settings__: access the current settings: `{ validateOnChange: true, validateSingle: true }`
288 |
289 | Additionally revalidation offers a number of helper functions to quickly update any values or validations.
290 |
291 | - __debounce__: a helper function for triggering asynchronous validations. The passed in asynchronous validation can be debounced by a specified time. i.e. 1000 ms.
292 |
293 | ```js
294 |
299 | ```
300 |
301 | - __updateValue__: update a specific field value. Important to note that no validation will run. Use _updateValueAndValidate_ if you need to update and validate of field. A name attribute must be defined on the element for _updateValue_ to update the value.
302 |
303 | ```js
304 |
311 | ```
312 |
313 | - __validateValue__: validates a specific field. Useful when validation should happen after an *onBlur* i.e.
314 | A name attribute must be defined on the element for _validateValue_ to validate the value.
315 |
316 | ```js
317 |
325 | ```
326 |
327 | - __updateValueAndValidate__: Updates and validates the value for the specified element.
328 | A name attribute must be defined on the element for _updateValueAndValidate_ to update the value.
329 |
330 | ```js
331 |
339 | ```
340 |
341 |
342 | Where and how to display the errors and when and how to validate is responsibility of the form not Revalidation.
343 | Another aspect is that the form props can be updated when needed.
344 |
345 | __NOTE:__ `updateForm` should be used carefully and only when needed. Make sure to reset or remove `updateForm` after
346 | applying the new form values.
347 |
348 |
349 | ```javascript
350 |
354 | ```
355 |
356 | Either define an initial state or use form props to define an actual form state. Revalidation will check for props first
357 | and then fallback to the initial state when none is found.
358 |
359 | __Revalidation__ also enables to pass in asynchronous error messages via the `asyncErrors` prop. As side effects are run outside of Revalidation itself, any error messages (from a dynamic validation or after submitting to a server and receiving errors) can be passed back into Revalidation.
360 |
361 | ```js
362 |
363 | // i.e. userNameExists is a function returning a promise and sends a request to validate if the username is available.
364 |
365 |
374 |
375 | ```
376 |
377 | __NOTE:__ A sensible approach with asynchronous validation functions is useful, Revalidation will not run any effects against
378 | an input field. Needed consideration include: when to run the side effects
379 | (dynamically or on submit) and how often to trigger an async validation (immediately on every change or debounced)
380 |
381 |
382 | More: Revalidation also works with deep nested data structure (see the deep nested data example)
383 |
384 | check the [example](https://github.com/25th-floor/revalidation/tree/master/example) for more detailed insight into how to build more advanced forms, f.e. validating dependent fields.
385 |
386 | Clone the repository go to the examples folder and run the following commands:
387 |
388 | ```js
389 | yarn install
390 | npm start.
391 | ```
392 |
393 | ### Demo
394 | Check the live [demo](http://revalidation.oss.25th-floor.com/example/demo/)
395 |
396 | ### Further Information
397 |
398 | For a deeper understanding of the underlying ideas and concepts:
399 |
400 | [Form Validation As A Higher Order Component Pt.1](https://medium.com/javascript-inside/form-validation-as-a-higher-order-component-pt-1-83ac8fd6c1f0)
401 |
402 | [Form Validation As A Higher Order Component Pt.2](https://medium.com/javascript-inside/form-validation-as-a-higher-order-component-pt-2-1edb7881870d)
403 |
404 |
405 | ### Credits
406 | Written by [A.Sharif](https://twitter.com/sharifsbeat)
407 |
408 | Original idea and support by [Stefan Oestreicher](https://twitter.com/thinkfunctional)
409 |
410 | Very special thanks to [Alex Booker](https://twitter.com/bookercodes) for providing input on the API and creating use cases.
411 |
412 | #### More
413 | __Revalidation__ is under development.
414 |
415 | The underlying synchronous validation is handled via [__Spected__](https://github.com/25th-floor/spected)
416 |
417 | #### Documentation
418 | [API](docs/API.md)
419 |
420 | [FAQ](docs/FAQ.md)
421 |
422 | ### License
423 |
424 | MIT
425 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-minimal
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # API
2 | ## `Revalidation(Component)`
3 |
4 | Creates an enhanced React Component containing validation and state keeping capabilities.
5 |
6 | #### Arguments
7 | 1. `Component` *(React Component)*: The React Component that will be enhanced with validation and state keeping functionality.
8 |
9 |
10 | #### Returns
11 |
12 | #### <HigherOrderComponentForm />
13 |
14 | `Higher Order React Component`: The provided component will be enhanced with a [`revalidation`](#revalidation) prop.
15 |
16 |
17 |
18 | #### Props
19 | - __`initialState`__ *(Object)*:
20 |
21 | The initial state, make sure to provide defaults for all form fields.
22 |
23 | - __`rules`__ *(Object)*:
24 |
25 | An object of rules, consisting of arrays containing predicate function / error message tuple, f.e. `{name: [[a => a.length > 2, 'Minimum length 3.']]}`
26 |
27 | - __`validateSingle`__ *(Boolean)*:
28 |
29 | if you need validation per field you can set the option to true (default is false).
30 |
31 | - __`validateOnChange`__: *(Boolean|Function)*:
32 |
33 | if you need instant validation as soon as props have changed set to true (default is false).
34 | Pass in a function to enable dynamic validation after the form has been submitted:
35 |
36 | ```js
37 |