Skip to content

Commit 2f6e3a5

Browse files
committed
feat(forms): show validation messages bellow form elements
1 parent 39e0c81 commit 2f6e3a5

File tree

6 files changed

+76
-20
lines changed

6 files changed

+76
-20
lines changed

src/forms/FormAutocomplete.jsx

+17-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState, useContext, useRef } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange, normalizeOptions } from './form-helpers';
3+
import { FormContext, handleInputChange, normalizeOptions, handleOnInvalid } from './form-helpers';
44
import { Dropdown } from '../mixed/Dropdown';
55
import { useOpenState } from '../utils/useOpenState';
66

@@ -29,19 +29,7 @@ export function FormAutocomplete({
2929
const inputRef = useRef(null);
3030

3131
return (
32-
<Dropdown
33-
isOpen={isOpen()}
34-
items={normalizeOptions(options, FormData).filter(filter(searchValue))}
35-
onSelect={({ value, label }) => {
36-
formState.update(name, value);
37-
setSearchValue(label);
38-
close();
39-
}}
40-
template={template}
41-
onTouchStart={() => setIgnoreBlur(true)}
42-
onMouseEnter={() => setIgnoreBlur(true)}
43-
onMouseLeave={() => setIgnoreBlur(false)}
44-
>
32+
<>
4533
<input
4634
{...{ required, name, id, placeholder }}
4735
type="text"
@@ -72,13 +60,27 @@ export function FormAutocomplete({
7260
close();
7361
}
7462
}}
63+
onInvalid={handleOnInvalid.bind(null, formState, name)}
7564
value={searchValue}
7665
role="combobox"
7766
aria-autocomplete="list"
7867
aria-expanded="false"
7968
autoComplete="off"
8069
/>
81-
</Dropdown>
70+
<Dropdown
71+
isOpen={isOpen()}
72+
items={normalizeOptions(options, FormData).filter(filter(searchValue))}
73+
onSelect={({ value, label }) => {
74+
formState.update(name, value);
75+
setSearchValue(label);
76+
close();
77+
}}
78+
template={template}
79+
onTouchStart={() => setIgnoreBlur(true)}
80+
onMouseEnter={() => setIgnoreBlur(true)}
81+
onMouseLeave={() => setIgnoreBlur(false)}
82+
/>
83+
</>
8284
);
8385
}
8486

src/forms/FormGroup.jsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useContext } from 'react';
22
import PropTypes from 'prop-types';
33
import { FormAutocomplete } from './FormAutocomplete';
44
import { FormCheckbox } from './FormCheckbox';
@@ -8,17 +8,24 @@ import { FormRadio } from './FormRadio';
88
import { FormSelect } from './FormSelect';
99
import { FormSwitch } from './FormSwitch';
1010
import { FormTextarea } from './FormTextarea';
11+
import { FormContext } from './form-helpers';
12+
13+
function FormGroup({ children, name, ...props }) {
14+
const formState = useContext(FormContext);
15+
const validationMessage = formState.getValidationMessage(name);
1116

12-
function FormGroup({ children, ...props }) {
1317
return (
1418
<div className="form-group">
1519
<FormLabel {...props} />
1620
{children}
21+
<div className="valid-feedback">&nbsp;</div>
22+
<div className="invalid-feedback">{validationMessage}</div>
1723
</div>
1824
);
1925
}
2026

2127
FormGroup.propTypes = {
28+
name: PropTypes.string.isRequired,
2229
children: PropTypes.element,
2330
};
2431

src/forms/FormInput.jsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { useContext, useRef, useEffect } from 'react';
1+
import React, { useContext } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange } from './form-helpers';
3+
import { FormContext, handleInputChange, handleOnInvalid } from './form-helpers';
44

55
export function FormInput({ id, type, name, placeholder, required, minLength, maxLength, min, max, pattern, step }) {
66
const formState = useContext(FormContext);
@@ -11,6 +11,7 @@ export function FormInput({ id, type, name, placeholder, required, minLength, ma
1111
className="form-control"
1212
onChange={handleInputChange.bind(null, formState)}
1313
value={formState.getValue(name) || ''}
14+
onInvalid={handleOnInvalid.bind(null, formState, name)}
1415
/>
1516
);
1617
}

src/forms/FormSelect.jsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useContext } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormContext, handleInputChange, normalizeOptions } from './form-helpers';
3+
import { FormContext, handleInputChange, normalizeOptions, handleOnInvalid } from './form-helpers';
44

55
export function FormSelect({ id, name, options, required, placeholder }) {
66
const formState = useContext(FormContext);
@@ -11,6 +11,7 @@ export function FormSelect({ id, name, options, required, placeholder }) {
1111
className="form-control"
1212
onChange={handleInputChange.bind(null, formState)}
1313
value={formState.getValue(name) || ''}
14+
onInvalid={handleOnInvalid.bind(null, formState, name)}
1415
>
1516
<option value="">{placeholder}</option>
1617

src/forms/form-helpers.js

+17
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import React, { useState } from 'react';
2+
import { useValueMap } from '../utils/useValueMap';
23

34
export const FormContext = React.createContext(null);
45

56
export function useForm(initialState) {
67
const [formState, setFormState] = useState(initialState);
8+
const { getValue: getValidationMessage, setValue: setValidationMessage } = useValueMap();
79

810
return {
911
update(name, value) {
12+
//target.setCustomValidity("");
1013
setFormState({
1114
...formState,
1215
[name]: value,
@@ -21,6 +24,16 @@ export function useForm(initialState) {
2124
reset() {
2225
setFormState(initialState);
2326
},
27+
updateElementValidationMessage(name, element) {
28+
if (element.valid) {
29+
setValidationMessage(name, '');
30+
} else {
31+
setValidationMessage(name, element.validationMessage);
32+
}
33+
},
34+
getValidationMessage(name) {
35+
return getValidationMessage(name);
36+
},
2437
};
2538
}
2639

@@ -32,6 +45,10 @@ export function handleInputChange(formState, event) {
3245
formState.update(name, value);
3346
}
3447

48+
export function handleOnInvalid(formState, name, { target }) {
49+
formState.updateElementValidationMessage(name, target);
50+
}
51+
3552
export function normalizeOptions(options, formData) {
3653
let _options = typeof options === 'function' ? options(formData) : options;
3754

src/utils/useValueMap.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useState } from 'react';
2+
3+
export function useValueMap() {
4+
const [valueMap, updateValueMap] = useState({});
5+
6+
function setValue(key, value) {
7+
updateValueMap((prevValueMap) => {
8+
return {
9+
...prevValueMap,
10+
[key]: value,
11+
};
12+
});
13+
}
14+
15+
function getValue(key) {
16+
return valueMap[key];
17+
}
18+
19+
return {
20+
setValue,
21+
getValue,
22+
setValueIfUnset(key, value) {
23+
if (!getValue(key)) {
24+
setValue(key, value);
25+
}
26+
},
27+
};
28+
}

0 commit comments

Comments
 (0)