Skip to content

Commit 995753d

Browse files
authored
Merge pull request #6 from pstrh/#1_improve_documentation
#1 improve documentation
2 parents 8145e79 + 15b1484 commit 995753d

File tree

13 files changed

+6235
-14
lines changed

13 files changed

+6235
-14
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ node_modules
88
.rts2_cache_umd
99
dist
1010
/HOW_TO_RELEASE.md
11+
*.iml

.npmignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ tsconfig.json
22
src
33
test
44
.idea
5-
examples
5+
examples
6+
*.iml

README.md

+177-12
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,215 @@
11
# react-constraint-validation
2-
**React Constraint Validation** is a small library that provides validation props for React components similar to the HTML Constraint Validation attributes.
2+
**React Constraint Validation** is a small library that aims to provide validation props for React components similar to the HTML Constraint Validation attributes.
33

44
### Features
55

6-
- HTML Constraint Validation attributes for React components (without refs)
6+
- HTML Constraint Validation attributes (like required, min, maxLength) for React components (without refs)
77
- Easily extensible with own validators
88
- Localisation of error messages and interpolation
99
- Written in Typescript
10-
- Perfect fit to Formik
10+
- To be used with Formik
1111

1212
### Use case
1313

1414
You don't want to declare field-level validation like this with your form framework:
1515

1616
<Field name="username" validate={[required, minLength2, maxLength20]} />
1717

18-
Instead you want to write:
18+
Instead you want to create Fields that directly support these validations with respective props:
1919

2020
<Field name="username" required minLength={2} maxLength={20} />
2121

2222
### Form framework needed
2323

24-
**React Constraint Validation** provides a HOC and some standard validators. The HOC can be used to enhance form components of your preferred form framework e.g. Formik.
24+
**React Constraint Validation** provides a experimental HOC and some standard validators. The HOC can be used to enhance form components of your preferred form framework e.g. Formik.
2525

26-
Limitation: Actually it currently only supports Formik ;) Support for other form frameworks like Redux-Form is planned with one of the next releases.
26+
Limitation: Actually it currently only supports Formik ;) Support for other form frameworks like React Final Form is planned with one of the next releases.
2727

2828
### Getting started
2929

3030
Add react-constraint-validation:
3131

32-
$ npm install react-constraint-validation --save
32+
npm i react-constraint-validation # or yarn add react-constraint-validation
3333

3434
Enhance a Formik field component with validation (should be done once per application):
3535

36-
const NumberField = withValidator({required, min, max },{number})(Field);
36+
import {withValidator, required, min, max, number, minLength, maxLength, email} from "react-constraint-validation";
37+
3738
const TextField = withValidator({required, minLength, maxLength})(Field);
38-
const EmailField = withValidator({required},{email})(Field);
39+
const NumberField = withValidator({required, min, max}, {number})(Field);
40+
const EmailField = withValidator({required}, {email})(Field);
3941

4042
Use your enhanced components in your form:
4143

4244
<TextField name="username" required minLength={2} maxLength={20} />
45+
<NumberField name="age" required min={20} max={120} />
46+
<EmailField name="email" required />
4347

4448
A complete example can be found in [examples/formik](https://github.com/pstrh/react-constraint-validation/blob/master/examples/formik/src/components/FieldLevelValidation.tsx).
4549

46-
### WIP
50+
### Validators
51+
52+
**React Constraint Validation** provides the following validations out-of-the-box:
53+
* `required` checks that the given value is not null, undefined or an empty string
54+
* `min` checks that the given number value is equal or greater than the given lower bound
55+
* `max` checks that the given value is smaller than or equal to the given upper bound
56+
* `minLength` checks that the length of the given string value is equal or greater than the given length
57+
* `maxLength` checks that the length of the given string value is smaller or equal to the given length
58+
* `pattern` checks if the given value matches a RegEx pattern
59+
* `number` checks if the given value is a number
60+
* `email` checks if the given value is a valid email (via a RegEx pattern)
61+
62+
### The withValidator HOC (experimental)
63+
64+
Fields wrapped with the withValidator HOC can be configured with two kinds of validators:
65+
- (optional) validators that can be activated via field props
66+
- default validators that should be always executed as they are inherent to the specific field type
67+
68+
Take a look at the examples:
69+
70+
const TextField = withValidator({required, minLength, maxLength})(Field);
71+
const NumberField = withValidator({required, min, max}, {number})(Field);
72+
73+
<TextField name="name" required />
74+
<NumberField name="amount" required min={100} />
75+
76+
NumberField has the optional validations `required`, `min` and `max`. These validations can (but don't have to) be set via field props.
77+
Whereas the `number` validation is defined as default validation and is always applied.
78+
Textfield on the other hand doesn't enforce a default validation. Here only the optional validations `required`,
79+
`minLength` and `maxLength` are configured on the field.
80+
81+
### Default error messages
82+
83+
The following object defines the default error messages:
84+
85+
```
86+
const defaultMessages: Messages = {
87+
"required": "{name} is required",
88+
"min": "{name} must be greater than {minValue}",
89+
"max": "{name} must be less than {maxValue}",
90+
"minLength": "{name} must have at least {minLength} characters",
91+
"maxLength": "{name} must have less than {maxLength} characters",
92+
"pattern": "{name} does not match the pattern {pattern}",
93+
"number": "{name} must be a number",
94+
"email": "{name} is not a valid email"
95+
};
96+
```
97+
98+
The placeholders `{name}`, `{minValue}`, `{maxValue}` and so on are replaced if the actual error message is created. `name` is the name of the form field. The other placeholders like `{minValue}` provide access to the respective validation constraints.
99+
100+
### Custom error messages
101+
102+
If you want to override the default error messages, just initialize the Validator with a custom error message object as shown in the following example:
103+
104+
```
105+
import * as Validator from "react-constraint-validation";
106+
107+
const customErrorMessages = {
108+
"required": "This field is required", // override without placeholder
109+
"number": "Enter only numbers in {name}"
110+
};
111+
112+
Validator.init(customErrorMessages);
113+
```
114+
115+
In the `init` method the custom error messages are merged with the default messages. As you can see in the `required` example, it's not necessary to use the placeholders of the default error
116+
messages. Usage of the placeholders in the custom error messages is optional.
117+
118+
If you want to create custom error messages for a specific field, you can just add an additional attribute following
119+
the pattern `required.<name>` (where name is the name attribute of the field).
120+
121+
```
122+
import * as Validator from "react-constraint-validation";
123+
124+
const customErrorMessages = {
125+
"required": "This field is required", // Error message for all other fields
126+
"required.firstName": "First name is required", // Error message for field 'firstName'
127+
"required.lastName": "Last name is required", // Error message for field 'lastName'
128+
"number": "Enter only numbers in {name}"
129+
};
130+
131+
Validator.init(customErrorMessages);
132+
```
133+
134+
E.g. for the required validation on the field 'firstName' react-constraint-validation will first check if the attribute
135+
`required.firstname` is contained in the errorMessage object. If `required.firstname` is not present it will
136+
retreive the error message configured via the `required` attribute.
137+
138+
### Localisation
139+
140+
Localisation of the error messages can be done in the same way as you customize the default error messages. Just initialize the Validator with an object of localized error messages as shown below:
141+
142+
```
143+
import * as Validator from "react-constraint-validation";
144+
145+
// should be content of your localisation file
146+
const germanMessages: Messages = {
147+
"required": "Bitte fülllen Sie dieses Feld",
148+
"min": "Der Wert des Feldes muss größer als {minValue} sein",
149+
"max": "Der Wert des Feldes darf nicht größer sein als {maxValue}",
150+
"minLength": "Bitte geben Sie mindestens {minLength} Zeichen ein",
151+
"maxLength": "Bitte geben Sie nicht mehr als {maxLength} Zeichen ein",
152+
"pattern": "Die Eingabe hat ein ungültiges Format",
153+
"number": "Hier sind nur Zahlen erlaubt",
154+
"email": "Die Email-Adresse ist nicht korrekt"
155+
};
156+
157+
// to be called if user locale changes
158+
Validator.init(germanMessages);
159+
```
160+
161+
### Create custom validators
162+
163+
If needed, you can create additional custom validators and configured them on your field via the withValidator HOC.
164+
165+
The following example illustrates how to create a custom validator. As a sample use case we create an "UpperCaseValidator".
166+
167+
```
168+
import { init, required, resolveErrorMessage, withValidator } from "react-constraint-validation";
169+
170+
// add new error message with key = "upperCase"
171+
init({
172+
"upperCase": "Given text must be upper case only"
173+
});
174+
175+
// create validator
176+
const upperCase = (name: string | undefined, value: string): string | undefined => {
177+
console.log("upperCase");
178+
if (value && value.toUpperCase() !== value) {
179+
// helper method to resolve error message with key = "upperCase"
180+
return resolveErrorMessage("upperCase", name, {});
181+
} else {
182+
return undefined;
183+
}
184+
}
185+
186+
// bind validator to field
187+
const TextField = withValidator({required, upperCase})(Field);
188+
189+
// activate upperCase validation in TextField
190+
<TextField name="myUpperCaseField" upperCase/>
191+
```
192+
193+
A custom validator must implement one of the following APIs:
194+
195+
type ValidationFunction = (name: string | undefined, value:any) => string | undefined;
196+
type ValidationFunctionWithParam = (name: string | undefined, value:any, param: any) => string | undefined;
197+
198+
The type `ValidationFunctionWithParam` can be used if the validator takes an argument like `minLenght={5}`.
199+
200+
### Field-Level vs. Form-Level validation
201+
202+
**React Constraint Validation** is designed to provide standard fields with build-in validation support (EmailField, NumberField and so on).
203+
204+
Therefore it might be useful if you need to build an application with many form fields or if you provide a component library for a number of different applications. If you only need to implement one or two small forms in your application using react-constraint-validation might be less useful.
205+
206+
Furthermore as this small library is designed to provide standard fields it is solely focused on field-level validation. Cross field or multi field validations is an aspect that depends on the individual form rather than on the single field. It's therefore better done on the form level
207+
(unfortunetly there is currently an issue with Formik as it execute form level validation before the field level validation).
208+
209+
### About
210+
211+
This project is experimental and still work in progress. Feel free to try. Feedback, suggestions and any kind of contribution is welcome.
47212

48-
This is a new project. The basic functionality is there and can be used.
213+
### License
49214

50-
Any feedback, suggestions and contributions are welcome.
215+
[MIT](./LICENSE)

examples/formik-advanced/.npmignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.cache
3+
dist

examples/formik-advanced/index.html

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"/>
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
8+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
9+
<title>Playground</title>
10+
</head>
11+
12+
<body>
13+
<div id="root"></div>
14+
<script src="./src/index.tsx"></script>
15+
</body>
16+
</html>

examples/formik-advanced/package.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "react-constraint-validation-formik-example",
3+
"version": "0.0.1",
4+
"description": "Example to show usage with formik",
5+
"main": "index.js",
6+
"author": "pstrh",
7+
"license": "MIT",
8+
"scripts": {
9+
"start": "parcel index.html",
10+
"build": "parcel build index.html"
11+
},
12+
"dependencies": {
13+
"@material-ui/core": "^4.5.1",
14+
"formik": "1.5.8",
15+
"formik-material-ui": "^0.0.22",
16+
"react-app-polyfill": "1.0.0"
17+
},
18+
"alias": {
19+
"react": "../../node_modules/react",
20+
"react-dom": "../../node_modules/react-dom/profiling",
21+
"scheduler/tracing": "../../node_modules/scheduler/tracing-profiling"
22+
},
23+
"devDependencies": {
24+
"@types/react": "16.8.15",
25+
"@types/react-dom": "16.8.4",
26+
"parcel": "1.12.3",
27+
"typescript": "3.4.5"
28+
}
29+
}

examples/formik-advanced/src/App.tsx

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import * as React from "react";
2+
import {AppBar, Typography, Toolbar, Container, Card, CardContent, Button, makeStyles} from "@material-ui/core";
3+
import {LanguageMenu} from "./components/LanguageMenu";
4+
import * as Validator from "../../../src";
5+
import {Messages} from "../../../src";
6+
import MyForm from "./components/MyForm";
7+
import {useEffect} from "react";
8+
9+
const useStyles = makeStyles(theme => ({
10+
root: {
11+
flexGrow: 1,
12+
},
13+
title: {
14+
flexGrow: 1,
15+
},
16+
}));
17+
18+
// German messages needed for this example
19+
const germanMessages: Messages = {
20+
"required": "{name} ist ein Pflichtfeld",
21+
"min": "{name} muss größer sein als {minValue}",
22+
"max": "{name} muss kleiner sein als {maxValue}",
23+
"minLength": "{name} muss mindestens {minLength} Zeichen haben",
24+
"maxLength": "{name} darf höchstens {maxLength} Zeichen haben",
25+
"number": "{name} muss eine Zahl sein",
26+
"email": "{name} ist keine gültige Email-Adresse",
27+
"upperCase": "Nur Großbuchstaben erlaubt"
28+
};
29+
30+
const customMessages: Messages = {
31+
"upperCase": "Only upper case please"
32+
}
33+
34+
const App = () => {
35+
const [locale, setLocale] = React.useState<string>("en");
36+
const classes = useStyles();
37+
38+
useEffect(() => {
39+
Validator.init(customMessages);
40+
}, [])
41+
42+
const handleLocaleChange = (locale: string) => {
43+
setLocale(locale)
44+
if (locale === "de") {
45+
Validator.init(germanMessages);
46+
} else {
47+
// reset
48+
Validator.init(customMessages);
49+
}
50+
};
51+
52+
return (
53+
<Container className={classes.root} maxWidth="sm">
54+
<AppBar position="static">
55+
<Toolbar>
56+
<Typography className={classes.title}>
57+
Example
58+
</Typography>
59+
<LanguageMenu activeLocale={locale} onLocaleChange={handleLocaleChange}/>
60+
</Toolbar>
61+
</AppBar>
62+
<Card>
63+
<CardContent>
64+
<MyForm key={locale}/>
65+
</CardContent>
66+
</Card>
67+
</Container>
68+
);
69+
};
70+
71+
export default App;

0 commit comments

Comments
 (0)