Use zod instead of yup (#2254)

* use zod instead of yup

* doc

* lint
This commit is contained in:
brendanlaschke
2023-10-27 10:26:32 +02:00
committed by GitHub
parent c04e866de3
commit 6a72c14af3
19 changed files with 61 additions and 67 deletions

View File

@ -303,6 +303,27 @@ import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';= import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';=
``` ```
## Schema Validation
[Zod](https://github.com/colinhacks/zod) is used as the schema validator for untyped objects:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## Breaking Changes ## Breaking Changes
Prioritize thorough manual testing before proceeding to guarantee that modifications havent caused disruptions elsewhere, given that tests have not yet been extensively integrated. Prioritize thorough manual testing before proceeding to guarantee that modifications havent caused disruptions elsewhere, given that tests have not yet been extensively integrated.

View File

@ -64,7 +64,6 @@
"uuid": "^9.0.0", "uuid": "^9.0.0",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"xlsx-ugnis": "^0.19.3", "xlsx-ugnis": "^0.19.3",
"yup": "^1.2.0",
"zod": "^3.22.2" "zod": "^3.22.2"
}, },
"scripts": { "scripts": {

View File

@ -1,9 +1,9 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form'; import { SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { yupResolver } from '@hookform/resolvers/yup'; import { zodResolver } from '@hookform/resolvers/zod';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import * as Yup from 'yup'; import { z } from 'zod';
import { authProvidersState } from '@/client-config/states/authProvidersState'; import { authProvidersState } from '@/client-config/states/authProvidersState';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
@ -28,19 +28,18 @@ export enum SignInUpStep {
Email = 'email', Email = 'email',
Password = 'password', Password = 'password',
} }
const validationSchema = Yup.object()
.shape({ const validationSchema = z
exist: Yup.boolean().required(), .object({
email: Yup.string() exist: z.boolean(),
.email('Email must be a valid email') email: z.string().email('Email must be a valid email'),
.required('Email must be a valid email'), password: z
password: Yup.string() .string()
.matches(PASSWORD_REGEX, 'Password must contain at least 8 characters') .regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
.required(),
}) })
.required(); .required();
type Form = Yup.InferType<typeof validationSchema>; type Form = z.infer<typeof validationSchema>;
export const useSignInUp = () => { export const useSignInUp = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -72,7 +71,7 @@ export const useSignInUp = () => {
defaultValues: { defaultValues: {
exist: false, exist: false,
}, },
resolver: yupResolver(validationSchema), resolver: zodResolver(validationSchema),
}); });
useEffect(() => { useEffect(() => {

View File

@ -2,7 +2,7 @@ import { isBoolean } from '@sniptt/guards';
import { FieldBooleanValue } from '../FieldMetadata'; import { FieldBooleanValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldBooleanValue = ( export const isFieldBooleanValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldBooleanValue => isBoolean(fieldValue); ): fieldValue is FieldBooleanValue => isBoolean(fieldValue);

View File

@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards';
import { FieldChipValue } from '../FieldMetadata'; import { FieldChipValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldChipValue = ( export const isFieldChipValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldChipValue => isString(fieldValue); ): fieldValue is FieldChipValue => isString(fieldValue);

View File

@ -2,7 +2,7 @@ import { isNull, isString } from '@sniptt/guards';
import { FieldDateValue } from '../FieldMetadata'; import { FieldDateValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldDateValue = ( export const isFieldDateValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldDateValue => isNull(fieldValue) || isString(fieldValue); ): fieldValue is FieldDateValue => isNull(fieldValue) || isString(fieldValue);

View File

@ -1,7 +1,7 @@
import { FieldDoubleTextValue } from '../FieldMetadata'; import { FieldDoubleTextValue } from '../FieldMetadata';
import { DoubleTextTypeResolver } from '../resolvers/DoubleTextTypeResolver'; import { DoubleTextTypeResolver } from '../resolvers/DoubleTextTypeResolver';
// TODO: add yup // TODO: add zod
export const isFieldDoubleTextValue = ( export const isFieldDoubleTextValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldDoubleTextValue => ): fieldValue is FieldDoubleTextValue =>

View File

@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards';
import { FieldEmailValue } from '../FieldMetadata'; import { FieldEmailValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldEmailValue = ( export const isFieldEmailValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldEmailValue => isString(fieldValue); ): fieldValue is FieldEmailValue => isString(fieldValue);

View File

@ -2,7 +2,7 @@ import { isNull, isNumber } from '@sniptt/guards';
import { FieldMoneyValue } from '../FieldMetadata'; import { FieldMoneyValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldMoneyValue = ( export const isFieldMoneyValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldMoneyValue => isNull(fieldValue) || isNumber(fieldValue); ): fieldValue is FieldMoneyValue => isNull(fieldValue) || isNumber(fieldValue);

View File

@ -2,7 +2,7 @@ import { isNull, isNumber } from '@sniptt/guards';
import { FieldNumberValue } from '../FieldMetadata'; import { FieldNumberValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldNumberValue = ( export const isFieldNumberValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldNumberValue => isNull(fieldValue) || isNumber(fieldValue); ): fieldValue is FieldNumberValue => isNull(fieldValue) || isNumber(fieldValue);

View File

@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards';
import { FieldPhoneValue } from '../FieldMetadata'; import { FieldPhoneValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldPhoneValue = ( export const isFieldPhoneValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldPhoneValue => isString(fieldValue); ): fieldValue is FieldPhoneValue => isString(fieldValue);

View File

@ -2,7 +2,7 @@ import { isNumber } from '@sniptt/guards';
import { FieldProbabilityValue } from '../FieldMetadata'; import { FieldProbabilityValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldProbabilityValue = ( export const isFieldProbabilityValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldProbabilityValue => isNumber(fieldValue); ): fieldValue is FieldProbabilityValue => isNumber(fieldValue);

View File

@ -2,7 +2,7 @@ import { isNull, isObject, isUndefined } from '@sniptt/guards';
import { FieldRelationValue } from '../FieldMetadata'; import { FieldRelationValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldRelationValue = ( export const isFieldRelationValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldRelationValue => ): fieldValue is FieldRelationValue =>

View File

@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards';
import { FieldTextValue } from '../FieldMetadata'; import { FieldTextValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldTextValue = ( export const isFieldTextValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldTextValue => isString(fieldValue); ): fieldValue is FieldTextValue => isString(fieldValue);

View File

@ -7,7 +7,7 @@ const urlV2Schema = z.object({
text: z.string(), text: z.string(),
}); });
// TODO: add yup // TODO: add zod
export const isFieldURLV2Value = ( export const isFieldURLV2Value = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldURLV2Value => urlV2Schema.safeParse(fieldValue).success; ): fieldValue is FieldURLV2Value => urlV2Schema.safeParse(fieldValue).success;

View File

@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards';
import { FieldURLValue } from '../FieldMetadata'; import { FieldURLValue } from '../FieldMetadata';
// TODO: add yup // TODO: add zod
export const isFieldURLValue = ( export const isFieldURLValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldURLValue => isString(fieldValue); ): fieldValue is FieldURLValue => isString(fieldValue);

View File

@ -3,10 +3,10 @@ import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getOperationName } from '@apollo/client/utilities'; import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { yupResolver } from '@hookform/resolvers/yup'; import { zodResolver } from '@hookform/resolvers/zod';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import * as Yup from 'yup'; import { z } from 'zod';
import { SubTitle } from '@/auth/components/SubTitle'; import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title'; import { Title } from '@/auth/components/Title';
@ -42,14 +42,14 @@ const StyledComboInputContainer = styled.div`
} }
`; `;
const validationSchema = Yup.object() const validationSchema = z
.shape({ .object({
firstName: Yup.string().required('First name can not be empty'), firstName: z.string().min(1, { message: 'First name can not be empty' }),
lastName: Yup.string().required('Last name can not be empty'), lastName: z.string().min(1, { message: 'Last name can not be empty' }),
}) })
.required(); .required();
type Form = Yup.InferType<typeof validationSchema>; type Form = z.infer<typeof validationSchema>;
export const CreateProfile = () => { export const CreateProfile = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -72,7 +72,7 @@ export const CreateProfile = () => {
firstName: currentUser?.firstName ?? '', firstName: currentUser?.firstName ?? '',
lastName: currentUser?.lastName ?? '', lastName: currentUser?.lastName ?? '',
}, },
resolver: yupResolver(validationSchema), resolver: zodResolver(validationSchema),
}); });
const onSubmit: SubmitHandler<Form> = useCallback( const onSubmit: SubmitHandler<Form> = useCallback(

View File

@ -3,8 +3,8 @@ import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getOperationName } from '@apollo/client/utilities'; import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { yupResolver } from '@hookform/resolvers/yup'; import { zodResolver } from '@hookform/resolvers/zod';
import * as Yup from 'yup'; import { z } from 'zod';
import { SubTitle } from '@/auth/components/SubTitle'; import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title'; import { Title } from '@/auth/components/Title';
@ -31,13 +31,13 @@ const StyledButtonContainer = styled.div`
width: 200px; width: 200px;
`; `;
const validationSchema = Yup.object() const validationSchema = z
.shape({ .object({
name: Yup.string().required('Name can not be empty'), name: z.string().min(1, { message: 'Name can not be empty' }),
}) })
.required(); .required();
type Form = Yup.InferType<typeof validationSchema>; type Form = z.infer<typeof validationSchema>;
export const CreateWorkspace = () => { export const CreateWorkspace = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -57,7 +57,7 @@ export const CreateWorkspace = () => {
defaultValues: { defaultValues: {
name: '', name: '',
}, },
resolver: yupResolver(validationSchema), resolver: zodResolver(validationSchema),
}); });
const onSubmit: SubmitHandler<Form> = useCallback( const onSubmit: SubmitHandler<Form> = useCallback(

View File

@ -15975,11 +15975,6 @@ prop-types@^15.6.1, prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.13.1" react-is "^16.13.1"
property-expr@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4"
integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==
property-information@^6.0.0: property-information@^6.0.0:
version "6.2.0" version "6.2.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.2.0.tgz#b74f522c31c097b5149e3c3cb8d7f3defd986a1d" resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.2.0.tgz#b74f522c31c097b5149e3c3cb8d7f3defd986a1d"
@ -18399,11 +18394,6 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
tiny-case@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03"
integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==
tiny-invariant@^1.0.6, tiny-invariant@^1.3.1: tiny-invariant@^1.0.6, tiny-invariant@^1.3.1:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
@ -18462,11 +18452,6 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==
tough-cookie@^4.0.0: tough-cookie@^4.0.0:
version "4.1.3" version "4.1.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
@ -19996,16 +19981,6 @@ yocto-queue@^1.0.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
yup@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/yup/-/yup-1.2.0.tgz#9e51af0c63bdfc9be0fdc6c10aa0710899d8aff6"
integrity sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==
dependencies:
property-expr "^2.0.5"
tiny-case "^1.0.3"
toposort "^2.0.2"
type-fest "^2.19.0"
zen-observable-ts@^1.2.5: zen-observable-ts@^1.2.5:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58"