├── .babelrc
├── .circleci
└── config.yml
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── src
├── CustomInput.js
├── FormState.js
├── index.js
├── index.md
└── index.test.js
├── styleguide.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "react"],
3 | "plugins": ["transform-object-rest-spread"]
4 | }
5 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: node:9
6 | steps:
7 | - checkout
8 | - run:
9 | name: Install dependencies
10 | command: 'yarn'
11 | - run:
12 | name: Build the package
13 | command: 'yarn build'
14 | - run:
15 | name: Run tests
16 | command: 'yarn test'
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 2017,
4 | "sourceType": "module",
5 | "ecmaFeatures": {
6 | "jsx": true,
7 | "impliedStrict": true
8 | }
9 | },
10 | "env": {
11 | "browser": true
12 | },
13 | "extends": [
14 | "eslint:recommended",
15 | "react-app"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # styleguide
2 | /styleguide
3 |
4 | # dependencies
5 | /node_modules
6 |
7 | # testing
8 | /coverage
9 |
10 | # production
11 | /dist
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
24 | # editor
25 |
26 | *.swn
27 | *.swo
28 | *.swp
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Derek W. Stavis
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 |
2 |
3 | # React Vanilla Form
4 | > An unobtrusive form serializer and validator that works by following standards.
5 |
6 |
7 |
8 | Vanilla Form is a form serialization and validation component built upon
9 | standards. To obtain the serialized form data the only thing you need to
10 | do is to declare your form controls (native or custom!) following the
11 | standard input interfaces: Using `name`, `value`, `htmlFor` and `role`
12 | properties.
13 |
14 | Wire `onSubmit` prop to `Form` component to get the serialized data from
15 | the form. Pass `validations` to display and catch errors in the form.
16 | Use `onChange` (or not) to get realtime data updates.
17 |
18 | ```jsx static
19 | import Form from 'react-vanilla-form'
20 |
21 |
36 | }
37 |
38 | )
39 | }
40 | }
41 | ```
42 |
43 | Then proceed to place components inside the form:
44 |
45 | ```jsx
46 | const FormState = require('./FormState.js');
47 |
48 |
49 |
52 |
53 |
54 |
57 |
58 |
59 |
62 |
63 |
64 |
65 |
66 | ```
67 |
68 | You can even nest objects infinite levels using the standard `fieldset` tag:
69 |
70 | ```jsx
71 | const FormState = require('./FormState.js');
72 |
73 |
74 |
77 |
78 |
79 |
96 |
97 |
98 | ```
99 |
100 | ## Use own components
101 |
102 | It's also possible to use custom input components as long as they follow the
103 | standard input interface:
104 |
105 |
106 | ```jsx static
107 | const Input = ({ name, type, onChange, title }) => (
108 |
109 |
110 | onChange(e.target.value) }} />
111 |
112 | )
113 |
114 | Input.defaultProps = {
115 | type: "text",
116 | }
117 | ```
118 |
119 | They should work as expected:
120 |
121 | ```jsx
122 | const FormState = require('./FormState.js');
123 | const Input = require('./CustomInput.js');
124 |
125 |
126 |
127 |
133 |
134 |
135 | ```
136 |
137 | ## Validating data
138 |
139 | Validation is triggered both on the fly (as the user types) and when the
140 | form is submitted. It's achieved through `validation` prop in `Form`, which
141 | accepts an object whose keys mirror form field structure, specifying the
142 | validation function or function array.
143 |
144 | The validation function receives the input value and in case of an error it
145 | should return an string with the message to be displayed for the user,
146 | otherwise return `false` (or a falsy value).
147 |
148 | To capture error messages for an `input`, use a sibling `label` component
149 | pointing to the `label` using `htmlFor` and define the `role` as `alert`.
150 | When using a custom input, error messages will be passed through via
151 | `error` prop. For customizing error properties, see more on next
152 | sections.
153 |
154 |
155 | ```jsx
156 | const FormState = require('./FormState.js');
157 | const Input = require('./CustomInput.js');
158 |
159 | function required (value) {
160 | return value ? false : 'This field is required!'
161 | }
162 |
163 | function isNumber (value) {
164 | return parseInt(value) ? false : 'Should be a number'
165 | }
166 |
167 |
174 |
175 |
176 |
185 |
186 |
187 | ```
188 |
189 | ### Custom error properties
190 |
191 | It is possible to receive the error message into the validated field via
192 | props by configuring the error prop name with `customErrorProp` prop.
193 |
194 | Let's say we want to improve the custom input component to include an
195 | `errorMessage` prop:
196 |
197 | ```jsx static
198 | const Input = ({ name, type, onChange, title, value, errorMessage }) => (
199 |
200 |
201 |
202 |
203 |
204 | )
205 | ```
206 |
207 | Configure `customErrorProp="errorMessage"` on the form with the prop name:
208 |
209 | ```jsx
210 | const FormState = require('./FormState.js');
211 | const Input = require('./CustomInput.js');
212 |
213 | function required (value) {
214 | return value ? false : 'This field is required!'
215 | }
216 |
217 |
221 |
222 |
223 |
224 | ```
225 |
226 | Now the custom input will receive the validation error as a prop.
227 |
228 | ### Run validations on different events
229 |
230 | By default, validations will run on `change` event, meaning that the
231 | feedback will be realtime, which sometimes is the desired behaviour,
232 | but sometimes might confuse users. For this cases, it's possible to
233 | change the event which will triggered via `validateOn` prop. The
234 | supported events are `change`, `focus`, `blur` and `submit`. Using
235 | `submit` will effectively disable realtime validation.
236 |
237 | ```jsx
238 | const FormState = require('./FormState.js');
239 | const Input = require('./CustomInput.js');
240 |
241 | function required (value) {
242 | return value ? false : 'This field is required!'
243 | }
244 |
245 | function isNumber (value) {
246 | return parseInt(value) ? false : 'Should be a number'
247 | }
248 |
249 |
260 |
261 |
265 |
266 |
267 | ```
268 |
269 | ### Keep validation errors on focus
270 |
271 | By default, when an input has an error message and it's touched (focused),
272 | the error message for the touched input will be cleared.
273 |
274 | If you want to keep validations errors, specify `keepErrorOnFocus`:
275 |
276 | ```jsx
277 | const FormState = require('./FormState.js');
278 | const Input = require('./CustomInput.js');
279 |
280 | function required (value) {
281 | return value ? false : 'This field is required!'
282 | }
283 |
284 | function isNumber (value) {
285 | return parseInt(value) ? false : 'Should be a number'
286 | }
287 |
288 |
300 |
301 |
305 |
306 |
307 |
308 | ```
309 |
310 | ## Setting form data
311 |
312 | It's possible to set the form data by passing an object whose keys mirror
313 | form field's structure via `data` prop.
314 |
315 | Currently the form keeps an internal state. When the `data` prop change
316 | the internal state will be synced with the prop's value.
317 |
318 | ```jsx
319 | const FormState = require('./FormState.js');
320 | const Input = require('./CustomInput.js');
321 |
322 | function required (value) {
323 | return value ? false : 'This field is required!'
324 | }
325 |
326 | function isNumber (value) {
327 | return parseInt(value) ? false : 'Should be a number'
328 | }
329 |
330 |
346 |
347 |
352 |
353 |
354 | ```
355 |
356 | ## Validating `data` prop
357 |
358 | When setting form data via `data` prop, by default the data will not be
359 | validated. Sometimes there are situations where you may want to validate
360 | and display errors, e.g.: server-side rendering. To validate the
361 | data set through `data` prop, set `validateDataProp` to true:
362 |
363 | ```jsx
364 | const FormState = require('./FormState.js');
365 | const Input = require('./CustomInput.js');
366 |
367 | function required (value) {
368 | return value ? false : 'This field is required!'
369 | }
370 |
371 | function isNumber (value) {
372 | return parseInt(value) ? false : 'Should be a number'
373 | }
374 |
375 |
393 |
394 |
398 |
399 |
400 | ```
401 |
402 | ## Setting errors manually
403 |
404 | It is possible to overwrite form errors using `errors` prop. This is
405 | useful for controlling your own validations on the parent component.
406 |
407 | > *Important*: in this case, you must control when the error is showed and cleared.
408 |
409 | ### Possible use cases:
410 |
411 | #### - async validations
412 |
413 | ```jsx
414 | const FormState = require('./FormState.js');
415 | const Input = require('./CustomInput.js');
416 |
417 | class ParentComponent extends React.Component {
418 | constructor (props) {
419 | super(props)
420 |
421 | this.state = {
422 | errors: {
423 | email: 'email is required'
424 | },
425 | loading: false
426 | }
427 |
428 | this.inputTimeout = 0;
429 | this.onEmailChange = this.onEmailChange.bind(this);
430 | this.validateEmail = this.validateEmail.bind(this);
431 | }
432 |
433 | onEmailChange(value) {
434 | if (this.inputTimeout) clearTimeout(this.inputTimeout)
435 |
436 | this.setState({ loading: true, errors: undefined })
437 |
438 | this.inputTimeout = setTimeout(() => {
439 | this.validateEmail(value)
440 | }, 500)
441 | }
442 |
443 | validateEmail(email) {
444 | const isValid = email === 'foo@example.com';
445 | const errors = isValid ? undefined : { email: 'email already exists' }
446 |
447 | this.setState({
448 | loading: false,
449 | errors
450 | })
451 | }
452 |
453 | render() {
454 | const { errors, loading } = this.state;
455 |
456 | return (
457 |
461 |
462 |
463 |
464 |
In this example, only foo@example.com is a valid email
465 |
466 | )
467 | }
468 | }
469 |
470 |
471 |
472 | ```
473 |
474 | #### - mixing custom errors with `this.props.validations`
475 |
476 | Using `errors` prop do not block you from using `validations` prop. You can mix both of them to reduce boilerplate and achieve more complex validations.
477 |
478 | > *Important*: `this.props.errors` messages have priority over `validations` ones.
479 |
480 | In the example below, we validate both email existence and its length.
481 |
482 | ```jsx
483 | const FormState = require('./FormState.js');
484 | const Input = require('./CustomInput.js');
485 |
486 | function required (value) {
487 | return value ? false : 'This field is required!'
488 | }
489 |
490 | function minEmailLength (value) {
491 | return value.length > 17 ? false : 'This email is too short' ;
492 | }
493 |
494 | class ParentComponent extends React.Component {
495 | constructor (props) {
496 | super(props)
497 |
498 | this.state = {
499 | errors: {
500 | email: undefined
501 | },
502 | loading: false
503 | }
504 |
505 | this.inputTimeout = 0;
506 | this.onEmailChange = this.onEmailChange.bind(this);
507 | this.validateEmail = this.validateEmail.bind(this);
508 | }
509 |
510 | onEmailChange(value) {
511 | if (this.inputTimeout) clearTimeout(this.inputTimeout)
512 |
513 | this.setState({ loading: true, errors: undefined })
514 |
515 | this.inputTimeout = setTimeout(() => {
516 | this.validateEmail(value)
517 | }, 500)
518 | }
519 |
520 | validateEmail(email) {
521 | const isValid = ['foo@example.com', 'foobar@example.com'].includes(email)
522 | const errors = isValid ? undefined : { email: 'This email already exists' }
523 |
524 | this.setState({
525 | loading: false,
526 | errors
527 | })
528 | }
529 |
530 | render() {
531 | const { errors, loading } = this.state;
532 |
533 | return (
534 |
542 |
543 |
544 |
545 |
First, try using "foo@example.com". Then, try "foobar@example.com".
546 |
Both foo@example.com and foobar@example.com are available to use, but only the last matches our custom criteria (email.length > 17 )