Skip to content

Commit 9a478c6

Browse files
authored
feat: automatic validation using validation constraint API (#32)
1 parent 6413769 commit 9a478c6

File tree

4 files changed

+78
-28
lines changed

4 files changed

+78
-28
lines changed

.changeset/quick-walls-taste.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@obosbbl/grunnmuren-react": minor
3+
---
4+
5+
add in built validation to TextField and TextArea using HTML validation constraint API

packages/react/src/TextArea/TextArea.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,34 @@ export interface TextAreaProps
1616
error?: string;
1717
/** Label for the form control */
1818
label: string;
19+
/** Automatically valdiate the form control using the HTML constraint validation API. @default true */
20+
validate?: boolean;
1921
}
2022

2123
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
2224
(props, ref) => {
23-
const { description, error, id: idProp, label, required, ...rest } = props;
25+
const {
26+
description,
27+
error,
28+
id: idProp,
29+
label,
30+
required,
31+
validate = true,
32+
...rest
33+
} = props;
2434

2535
const ownRef = useRef(null);
2636

27-
const { validity } = useFormControlValidity(ownRef);
37+
const { validity, validationMessage } = useFormControlValidity(
38+
ownRef,
39+
validate,
40+
);
2841

2942
const id = useFallbackId(idProp);
30-
const helpTextId = id + '-help';
31-
const errorMsgId = id + '-err-msg';
43+
const helpTextId = id + 'help';
44+
const errorMsgId = id + 'err';
45+
46+
const errorMsg = error ?? validationMessage;
3247

3348
return (
3449
<div className="grid gap-2">
@@ -54,7 +69,9 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
5469
{description && (
5570
<FormHelperText id={helpTextId}>{description}</FormHelperText>
5671
)}
57-
{error && <FormErrorMessage id={errorMsgId}>{error}</FormErrorMessage>}
72+
{errorMsg && (
73+
<FormErrorMessage id={errorMsgId}>{errorMsg}</FormErrorMessage>
74+
)}
5875
</div>
5976
);
6077
},

packages/react/src/TextField/TextField.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface TextFieldProps
1717
/** Label for the form control */
1818
label: string;
1919
prefix?: string;
20+
/** Automatically valdiate the form control using the HTML constraint validation API. @default true */
21+
validate?: boolean;
2022
}
2123

2224
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
@@ -28,16 +30,22 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
2830
label,
2931
required,
3032
type = 'text',
33+
validate = true,
3134
...rest
3235
} = props;
3336

3437
const ownRef = useRef(null);
3538

36-
const { validity } = useFormControlValidity(ownRef);
39+
const { validity, validationMessage } = useFormControlValidity(
40+
ownRef,
41+
validate,
42+
);
3743

3844
const id = useFallbackId(idProp);
39-
const helpTextId = id + '-help';
40-
const errorMsgId = id + '-err';
45+
const helpTextId = id + 'help';
46+
const errorMsgId = id + 'err';
47+
48+
const errorMsg = error ?? validationMessage;
4149

4250
return (
4351
<div className="grid gap-2">
@@ -62,7 +70,9 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
6270
{description && (
6371
<FormHelperText id={helpTextId}>{description}</FormHelperText>
6472
)}
65-
{error && <FormErrorMessage id={errorMsgId}>{error}</FormErrorMessage>}
73+
{errorMsg && (
74+
<FormErrorMessage id={errorMsgId}>{errorMsg}</FormErrorMessage>
75+
)}
6676
</div>
6777
);
6878
},
Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, RefObject } from 'react';
1+
import { useState, useEffect, RefObject, useCallback } from 'react';
22

33
type Validity = 'indeterminate' | 'invalid' | 'valid';
44

@@ -10,32 +10,50 @@ type Validity = 'indeterminate' | 'invalid' | 'valid';
1010
*/
1111
export function useFormControlValidity(
1212
ref: RefObject<HTMLElement & { checkValidity(): boolean }>,
13+
enabled = true,
1314
) {
1415
const [validity, setValidity] = useState<Validity>('indeterminate');
16+
const [validationMessage, setValidationMessage] = useState<string>();
1517

16-
const onBlur = (event: FocusEvent) => {
17-
// this triggers an invalid event, so if it's invalid it's handled by the `onInvalid` handler
18-
const isValid = (event.target as HTMLInputElement).checkValidity();
19-
20-
if (isValid) {
21-
setValidity('valid');
22-
}
23-
};
24-
25-
const onInput = (event: Event) => {
26-
if (validity !== 'indeterminate') {
18+
const onBlur = useCallback(
19+
(event: FocusEvent) => {
2720
// this triggers an invalid event, so if it's invalid it's handled by the `onInvalid` handler
2821
const isValid = (event.target as HTMLInputElement).checkValidity();
2922

30-
if (isValid) {
23+
if (isValid && enabled) {
3124
setValidity('valid');
25+
setValidationMessage(undefined);
26+
}
27+
},
28+
[enabled],
29+
);
30+
31+
const onInput = useCallback(
32+
(event: Event) => {
33+
if (enabled && validity !== 'indeterminate') {
34+
// this triggers an invalid event, so if it's invalid it's handled by the `onInvalid` handler
35+
const isValid = (event.target as HTMLInputElement).checkValidity();
36+
37+
if (isValid) {
38+
setValidity('valid');
39+
setValidationMessage(undefined);
40+
}
3241
}
33-
}
34-
};
42+
},
43+
[enabled, validity],
44+
);
3545

36-
const onInvalid = (/*event: Event*/) => {
37-
setValidity('invalid');
38-
};
46+
const onInvalid = useCallback(
47+
(event: Event) => {
48+
if (enabled) {
49+
event.preventDefault();
50+
const message = (event.target as HTMLInputElement).validationMessage;
51+
setValidationMessage(message);
52+
setValidity('invalid');
53+
}
54+
},
55+
[enabled],
56+
);
3957

4058
useEffect(() => {
4159
const { current } = ref;
@@ -50,5 +68,5 @@ export function useFormControlValidity(
5068
};
5169
});
5270

53-
return { validity };
71+
return { validity, validationMessage };
5472
}

0 commit comments

Comments
 (0)