From 82d882b748f8cad640b47c9e105bd0865f8cca15 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Tue, 27 Apr 2021 19:48:48 -0400 Subject: [PATCH] fix(useformstate): properly assert that field types inferred from yup schema are defined Also adds a "Complex field types and validations" example to the docs --- .../useFormState/useFormState.stories.mdx | 19 ++++ .../useFormState/useFormState.stories.tsx | 99 ++++++++++++++++++- src/hooks/useFormState/useFormState.ts | 4 +- tsconfig.json | 2 +- 4 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/hooks/useFormState/useFormState.stories.mdx b/src/hooks/useFormState/useFormState.stories.mdx index 95297e5..ae03d76 100644 --- a/src/hooks/useFormState/useFormState.stories.mdx +++ b/src/hooks/useFormState/useFormState.stories.mdx @@ -5,6 +5,7 @@ import { PatternFlyTextFields, PatternFlyTextFieldsWithHelpers, AsyncPrefilling, + ComplexFieldTypes, } from './useFormState.stories.tsx'; import GithubLink from '../../../.storybook/helpers/GithubLink'; @@ -115,6 +116,24 @@ Most custom non-text fields can use `getFormGroupProps` even if the other helper +### Complex field types and validations + +You can store any type of value in a form field, but you need to find the right `yup` schema type to use. `yup.string()`, `yup.number()` and `yup.boolean()` are straightforward, +but you may also need to use things like `yup.array()`, `yup.date()`, `yup.object()`. (Note: only use `yup.object` when you need specific validation on each property in the object, which may be better designed as individual useFormField hooks). +If nothing else fits, you can always use `yup.mixed()` which allows you to use any type but does not validate the value for you. If you use `mixed`, be sure to add your own validations with schema methods like `required`, `oneOf`, and `test`. +See the [yup API documentation](https://github.com/jquense/yup#api) for more information. + +This example includes a mixed field and an array field (leveraging the `useSelectionState` hook also provided by lib-ui). It has custom validations to make sure the store you select is open and that you don't select more than 2 items (for some reason). + +Note that the type parameters passed here to `useFormField` are not required, because the field type can usually be inferred from the yup schema. +However, since the schema are non-trivial we're specifying types here explicitly just to be sure the schema match up with what we're expecting. +If you're having trouble with yup schema types resolving to `T | undefined` instead of `T`, try chaining `.default(someDefault)` to tell yup what to validate against if there is an undefined value. +[Learn more about yup's TypeScript support here](https://github.com/jquense/yup/blob/master/docs/typescript.md). + + + + + ### Pre-filling a form asynchronously If the form's initial values are known when it is first rendered, they can simply be passed as the `initialValue` arguments of each `useFormField` call. diff --git a/src/hooks/useFormState/useFormState.stories.tsx b/src/hooks/useFormState/useFormState.stories.tsx index 454b241..5459f22 100644 --- a/src/hooks/useFormState/useFormState.stories.tsx +++ b/src/hooks/useFormState/useFormState.stories.tsx @@ -8,6 +8,7 @@ import { getFormGroupProps, getTextInputProps, getTextAreaProps, + useSelectionState, } from '../..'; export const BasicTextFields: React.FunctionComponent = () => { @@ -29,7 +30,7 @@ export const BasicTextFields: React.FunctionComponent = () => { onBlur={() => form.fields.name.setIsTouched(true)} /> {!form.fields.name.isValid ? ( -

{form.fields.name.error.message}

+

{form.fields.name.error?.message}

) : null}
@@ -42,7 +43,7 @@ export const BasicTextFields: React.FunctionComponent = () => { onBlur={() => form.fields.description.setIsTouched(true)} /> {!form.fields.description.isValid ? ( -

{form.fields.description.error.message}

+

{form.fields.description.error?.message}

) : null}
+ + + ); +}; diff --git a/src/hooks/useFormState/useFormState.ts b/src/hooks/useFormState/useFormState.ts index 64e6fe7..3c64a18 100644 --- a/src/hooks/useFormState/useFormState.ts +++ b/src/hooks/useFormState/useFormState.ts @@ -52,7 +52,7 @@ export interface IFormState { export const useFormField = ( initialValue: T, - schema: yup.AnySchema, + schema: yup.AnySchema | yup.AnySchema, options: { initialTouched?: boolean } = {} ): IFormField => { const [initializedValue, setInitializedValue] = React.useState(initialValue); @@ -72,7 +72,7 @@ export const useFormField = ( setValue(initializedValue); setIsTouched(options.initialTouched || false); }, - schema, + schema: schema.defined(), }; }; diff --git a/tsconfig.json b/tsconfig.json index 84cb785..b55c990 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,5 +20,5 @@ "skipLibCheck": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/**/*.stories.tsx", "src/**/*.test.tsx"] + "exclude": ["node_modules", "dist"] }