Skip to content

Commit 81b52be

Browse files
committed
fix: build schema
1 parent 4bb40e1 commit 81b52be

File tree

6 files changed

+71
-40
lines changed

6 files changed

+71
-40
lines changed

src/components/form/EmailField.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { string as YupString } from "yup"
55

66
import TextField, { type TextFieldProps } from "./TextField"
77

8-
export type EmailFieldProps = Omit<TextFieldProps, "type" | "name" | "schema"> &
8+
export type EmailFieldProps = Omit<
9+
TextFieldProps,
10+
"type" | "name" | "baseSchema" | "allowedChars"
11+
> &
912
Partial<Pick<TextFieldProps, "name">>
1013

1114
const EmailField: FC<EmailFieldProps> = ({
@@ -18,7 +21,7 @@ const EmailField: FC<EmailFieldProps> = ({
1821
return (
1922
<TextField
2023
type="email"
21-
schema={YupString().email()}
24+
baseSchema={YupString().email()}
2225
name={name}
2326
label={label}
2427
placeholder={placeholder}

src/components/form/FirstNameField.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,20 @@ import { InputAdornment } from "@mui/material"
33
import type { FC } from "react"
44

55
import TextField, { type TextFieldProps } from "./TextField"
6-
import { firstNameSchema } from "../../schemas/user"
76

8-
export type FirstNameFieldProps = Omit<
9-
TextFieldProps,
10-
"type" | "name" | "schema"
11-
> &
7+
export type FirstNameFieldProps = Omit<TextFieldProps, "type" | "name"> &
128
Partial<Pick<TextFieldProps, "name">>
139

1410
const FirstNameField: FC<FirstNameFieldProps> = ({
1511
name = "first_name",
1612
label = "First name",
1713
placeholder = "Enter your first name",
14+
maxChars = 150,
1815
InputProps = {},
1916
...otherTextFieldProps
2017
}) => {
2118
return (
2219
<TextField
23-
schema={firstNameSchema}
2420
name={name}
2521
label={label}
2622
placeholder={placeholder}

src/components/form/OtpField.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { type FC } from "react"
2-
import { string as YupString } from "yup"
32

43
import TextField, { type TextFieldProps } from "./TextField"
54

65
export type OtpFieldProps = Omit<
76
TextFieldProps,
8-
"name" | "schema" | "required"
7+
"name" | "schema" | "required" | "minChars" | "maxChars" | "allowedChars"
98
> &
109
Partial<Pick<TextFieldProps, "name">>
1110

@@ -18,7 +17,9 @@ const OtpField: FC<OtpFieldProps> = ({
1817
<TextField
1918
name={name}
2019
label={label}
21-
schema={YupString().matches(/^[0-9]{6}$/, "Must be exactly 6 digits.")}
20+
allowedChars="0-9"
21+
minChars={6}
22+
maxChars={6}
2223
placeholder={placeholder}
2324
required
2425
{...otherTextFieldProps}

src/components/form/PasswordField.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ import {
44
} from "@mui/icons-material"
55
import { IconButton, InputAdornment } from "@mui/material"
66
import { useState, type FC } from "react"
7-
import { string as YupString } from "yup"
87

98
import RepeatField, { type RepeatFieldProps } from "./RepeatField"
109
import TextField, { type TextFieldProps } from "./TextField"
1110

1211
export type PasswordFieldProps = Omit<
1312
TextFieldProps,
14-
"type" | "name" | "schema" | "autoComplete"
13+
"type" | "name" | "autoComplete"
1514
> &
16-
Partial<Pick<TextFieldProps, "name" | "schema">> & {
15+
Partial<Pick<TextFieldProps, "name">> & {
1716
withRepeatField?: boolean
1817
repeatFieldProps?: Omit<RepeatFieldProps, "name" | "type">
1918
}
@@ -22,7 +21,6 @@ const PasswordField: FC<PasswordFieldProps> = ({
2221
name = "password",
2322
label = "Password",
2423
placeholder = "Enter your password",
25-
schema = YupString(),
2624
InputProps = {},
2725
withRepeatField = false,
2826
repeatFieldProps = {},
@@ -51,7 +49,6 @@ const PasswordField: FC<PasswordFieldProps> = ({
5149
type={type}
5250
name={name}
5351
label={label}
54-
schema={schema}
5552
placeholder={placeholder}
5653
InputProps={{ endAdornment, ...InputProps }}
5754
{...otherTextFieldProps}

src/components/form/TextField.tsx

+58-20
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import {
55
import { Field, type FieldConfig, type FieldProps } from "formik"
66
import { type FC, useState, useEffect } from "react"
77
import {
8-
type ArraySchema,
98
type StringSchema,
109
type ValidateOptions,
1110
array as YupArray,
12-
type Schema,
11+
string as YupString,
1312
} from "yup"
1413

1514
import { schemaToFieldValidator } from "../../utils/form"
@@ -26,65 +25,104 @@ export type TextFieldProps = Omit<
2625
| "helperText"
2726
> & {
2827
name: string
29-
schema: StringSchema
28+
baseSchema?: StringSchema
3029
validateOptions?: ValidateOptions
3130
dirty?: boolean
3231
split?: string | RegExp
3332
unique?: boolean
3433
uniqueCaseInsensitive?: boolean
34+
allowedChars?: string
35+
minChars?: number
36+
maxChars?: number
37+
minValues?: number
38+
maxValues?: number
3539
}
3640

3741
// https://formik.org/docs/examples/with-material-ui
3842
const TextField: FC<TextFieldProps> = ({
3943
id,
4044
name,
41-
schema,
45+
baseSchema = YupString(),
4246
type = "text",
4347
required = false,
4448
dirty = false,
4549
unique = false,
4650
uniqueCaseInsensitive = false,
51+
allowedChars,
52+
minChars,
53+
maxChars = 1000,
4754
split,
55+
minValues,
56+
maxValues = 1000,
4857
validateOptions,
4958
...otherTextFieldProps
5059
}) => {
5160
const [initialValue, setInitialValue] = useState<string | string[]>("")
5261

5362
const dotPath = name.split(".")
5463

55-
let _schema: Schema = schema
56-
if (split) {
57-
_schema = YupArray().of(_schema)
58-
if (unique || uniqueCaseInsensitive) {
59-
_schema = _schema.test({
64+
function buildSchema() {
65+
// Build schema for a single string.
66+
let stringSchema = baseSchema
67+
// 1: Validate string has min length.
68+
if (required || minChars)
69+
stringSchema = stringSchema.required().min(minChars ?? 1)
70+
// 2: Validate string has max length.
71+
stringSchema = stringSchema.max(maxChars)
72+
// 3. Validate string has allowed characters.
73+
if (allowedChars)
74+
stringSchema = stringSchema.matches(new RegExp(`[${allowedChars}]*`))
75+
// 4: Validate string is dirty.
76+
if (dirty && !split)
77+
stringSchema = stringSchema.notOneOf(
78+
[initialValue as string],
79+
"cannot be initial value",
80+
)
81+
// Return schema for a single string.
82+
if (!split) return stringSchema
83+
84+
// Build schema for an array of strings.
85+
let arraySchema = YupArray().of(stringSchema)
86+
// 1: Validate string array has min length.
87+
if (required || minValues)
88+
arraySchema = arraySchema.required().min(minValues ?? 1)
89+
// 2: Validate string array has max length.
90+
arraySchema = arraySchema.max(maxValues)
91+
// 3: Validate string array has unique values.
92+
if (unique || uniqueCaseInsensitive)
93+
arraySchema = arraySchema.test({
6094
message: "cannot have duplicates",
6195
test: values => {
62-
if (Array.isArray(values) && values.length >= 2) {
96+
if (
97+
Array.isArray(values) &&
98+
values.length >= 2 &&
99+
values.every(value => typeof value === "string")
100+
) {
63101
return (
64102
new Set(
65-
uniqueCaseInsensitive && typeof values[0] === "string"
103+
uniqueCaseInsensitive
66104
? values.map(value => value.toLowerCase())
67105
: values,
68106
).size === values.length
69107
)
70108
}
71-
72109
return true
73110
},
74111
})
75-
}
76-
}
77-
if (required) {
78-
_schema = _schema.required()
79-
if (split) _schema = (_schema as ArraySchema<string[], any>).min(1)
112+
// 4: Validate string array is dirty.
113+
if (dirty)
114+
arraySchema = arraySchema.notOneOf(
115+
[initialValue as string[]],
116+
"cannot be initial value",
117+
)
118+
// Return schema for an array of strings.
119+
return arraySchema
80120
}
81-
if (dirty)
82-
_schema = _schema.notOneOf([initialValue], "cannot be initial value")
83121

84122
const fieldConfig: FieldConfig = {
85123
name,
86124
type,
87-
validate: schemaToFieldValidator(_schema, validateOptions),
125+
validate: schemaToFieldValidator(buildSchema(), validateOptions),
88126
}
89127

90128
const _Field: FC<FieldProps> = ({ form }) => {

src/schemas/user.ts

-4
This file was deleted.

0 commit comments

Comments
 (0)