Skip to content

Commit

Permalink
refactor: infer field value from field schema
Browse files Browse the repository at this point in the history
BREAKING CHANGE: renamed select&multiselect field as those are not 'real'. For multiselect there is arrayField and the selectField is regular stringField.

selectFieldHook will be made generic to support boolean, number, string, date (all primitive values) selection from list of options.
  • Loading branch information
MiroslavPetrik committed Mar 3, 2023
1 parent f54871e commit 378d1b5
Show file tree
Hide file tree
Showing 31 changed files with 178 additions and 138 deletions.
File renamed without changes.
22 changes: 22 additions & 0 deletions src/fields/array-field/arrayField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ZodArray, z } from "zod";

import { ValidatedFieldAtomConfig, validatedFieldAtom } from "..";
import { ZodParams, defaultParams } from "../zodParams";

export const arrayField = <ElementSchema extends z.Schema>({
required_error = defaultParams.required_error,
elementSchema,
...config
}: { elementSchema: ElementSchema } & Partial<
ValidatedFieldAtomConfig<
ZodArray<ElementSchema, "atleastone">,
ZodArray<ElementSchema, "many">
>
> &
ZodParams) =>
validatedFieldAtom({
value: [],
schema: z.array(elementSchema).nonempty(required_error),
optionalSchema: z.array(elementSchema),
...config,
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { multiSelectField } from "./multiSelectField";
import { stringArrayField } from "./stringArrayField";
import {
MultiSelectFieldProps,
useMultiSelectFieldProps,
} from "./useMultiSelectFieldProps";
StringArrayFieldProps,
useArrayFieldProps,
} from "./useArrayFieldProps";
import { FieldErrors } from "../../components/field-errors";
import { useOptions } from "../../hooks";
import { FormStory, fixArgs, meta } from "../../scenarios/StoryForm";
import { useOptions } from "../select-field";

export default {
...meta,
title: "fields/multiSelectField/checkboxGroup",
title: "fields/arrayField/checkboxGroup",
};

const CheckboxGroup = <Option,>({
Expand All @@ -18,8 +18,8 @@ const CheckboxGroup = <Option,>({
getValue,
getLabel,
options,
}: MultiSelectFieldProps<Option>) => {
const props = useMultiSelectFieldProps(field);
}: StringArrayFieldProps<Option>) => {
const props = useArrayFieldProps(field);

const { renderOptions } = useOptions(field, {
getValue,
Expand Down Expand Up @@ -67,7 +67,7 @@ const languagesOptions = [
export const Required: FormStory = {
args: fixArgs({
fields: {
languages: multiSelectField(),
languages: stringArrayField(),
},
children: ({ fields }) => (
<CheckboxGroup
Expand All @@ -84,7 +84,7 @@ export const Required: FormStory = {
export const Optional: FormStory = {
args: fixArgs({
fields: {
attachment: multiSelectField({
attachment: stringArrayField({
optional: true,
}),
},
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions src/fields/array-field/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./arrayField";
export * from "./useArrayFieldProps";
19 changes: 19 additions & 0 deletions src/fields/array-field/stringArrayField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ExtractAtomValue } from "jotai";
import { z } from "zod";

import { arrayField } from "./arrayField";
import { ZodParams } from "../zodParams";

export const stringArrayField = (
params: ZodParams & {
value?: string[];
optional?: boolean;
name?: string;
} = {}
) => arrayField({ elementSchema: z.string(), ...params });

export type StringArrayField = ReturnType<typeof stringArrayField>;

export type StringArrayFieldValue = ExtractAtomValue<
ExtractAtomValue<StringArrayField>["value"]
>;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ChangeEvent } from "react";

import { MultiSelectFieldAtom } from "./multiSelectField";
import { StringArrayField, StringArrayFieldValue } from "./stringArrayField";
import { FieldProps, OptionProps, useFieldProps } from "../../hooks";

// TODO: make not dependent on checkbox input event
Expand All @@ -9,10 +9,8 @@ const getEventValue = (event: ChangeEvent<HTMLInputElement>, value: string[]) =>
? [...value, event.target.value]
: value.filter((val) => val != event.target.value);

export type MultiSelectFieldProps<Option, OptionValue = string> = FieldProps<
MultiSelectFieldAtom<OptionValue>
> &
OptionProps<Option, OptionValue, OptionValue[]>;
export type StringArrayFieldProps<Option> = FieldProps<StringArrayField> &
OptionProps<Option, string, StringArrayFieldValue>;

export const useMultiSelectFieldProps = (field: MultiSelectFieldAtom) =>
export const useArrayFieldProps = (field: StringArrayField) =>
useFieldProps<string[], HTMLInputElement>(field, getEventValue);
2 changes: 1 addition & 1 deletion src/fields/boolean-field/booleanField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useBooleanFieldProps } from "./useBooleanFieldProps";
import { FieldLabel } from "../../components";
import { FieldErrors } from "../../components/field-errors";
import { FormStory, fixArgs, meta } from "../../scenarios/StoryForm";
import { SelectFieldProps, useSelectOptions } from "../select-field";
import { SelectFieldProps, useSelectOptions } from "../string-field";

export default {
...meta,
Expand Down
17 changes: 8 additions & 9 deletions src/fields/boolean-field/booleanField.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { z } from "zod";
import { ExtractAtomValue } from "jotai";
import { ZodBoolean, z } from "zod";

import {
ValidatedFieldAtom,
ValidatedFieldAtomConfig,
validatedFieldAtom,
} from "..";
import { ValidatedFieldAtomConfig, validatedFieldAtom } from "..";
import { ZodParams, defaultParams } from "../zodParams";

export type BooleanFieldValue = boolean | undefined;
export type BooleanFieldAtom = ReturnType<typeof booleanField>;

export type BooleanFieldAtom = ValidatedFieldAtom<BooleanFieldValue>;
export type BooleanFieldValue = ExtractAtomValue<
ExtractAtomValue<BooleanFieldAtom>["value"]
>;

export const booleanField = ({
required_error = defaultParams.required_error,
...config
}: Partial<ValidatedFieldAtomConfig<BooleanFieldValue>> & ZodParams = {}) =>
}: Partial<ValidatedFieldAtomConfig<ZodBoolean>> & ZodParams = {}) =>
validatedFieldAtom({
value: undefined,
schema: z.boolean({ required_error }),
Expand Down
23 changes: 14 additions & 9 deletions src/fields/checkbox-field/checkboxField.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { z } from "zod";
import { ExtractAtomValue } from "jotai";
import { ZodBoolean, ZodLiteral, z } from "zod";

import {
ValidatedFieldAtom,
ValidatedFieldAtomConfig,
validatedFieldAtom,
} from "..";
import { ValidatedFieldAtomConfig, validatedFieldAtom } from "..";
import { ZodParams, defaultParams } from "../zodParams";

export type CheckboxFieldValue = boolean;
export type CheckboxFieldAtom = ReturnType<typeof checkboxField>;

export type CheckboxFieldAtom = ValidatedFieldAtom<CheckboxFieldValue>;
export type CheckboxFieldValue = ExtractAtomValue<
ExtractAtomValue<CheckboxFieldAtom>["value"]
>;

export const checkboxField = ({
required_error = defaultParams.required_error,
...config
}: Partial<ValidatedFieldAtomConfig<CheckboxFieldValue>> & ZodParams = {}) =>
}: Partial<
Omit<
ValidatedFieldAtomConfig<ZodLiteral<true>, ZodBoolean>,
"schema" | "optionalSchema" | "validate"
>
> &
ZodParams = {}) =>
validatedFieldAtom({
value: false,
/**
Expand Down
18 changes: 10 additions & 8 deletions src/fields/file-field/fileField.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { ExtractAtomValue } from "jotai";
import { z } from "zod";

import {
ValidatedFieldAtom,
ValidatedFieldAtomConfig,
validatedFieldAtom,
} from "..";
import { ValidatedFieldAtomConfig, validatedFieldAtom } from "..";
import { ZodParams, defaultParams } from "../zodParams";

export type FileFieldValue = FileList | undefined;
export type FileFieldAtom = ReturnType<typeof fileField>;

export type FileFieldAtom = ValidatedFieldAtom<FileFieldValue>;
export type FileFieldValue = ExtractAtomValue<
ExtractAtomValue<FileFieldAtom>["value"]
>;

const fileListInstanceSchema = z.instanceof(FileList);
type ZodFileListInstance = typeof fileListInstanceSchema;

export const fileField = ({
required_error = defaultParams.required_error,
...config
}: Partial<ValidatedFieldAtomConfig<FileFieldValue>> & ZodParams = {}) =>
}: Partial<ValidatedFieldAtomConfig<ZodFileListInstance>> & ZodParams = {}) =>
validatedFieldAtom({
value: undefined,
schema: z.instanceof(FileList, required_error),
Expand Down
4 changes: 2 additions & 2 deletions src/fields/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export * from "./boolean-field";
export * from "./checkbox-field";
export * from "./file-field";
export * from "./multi-select-field";
export * from "./array-field";
export * from "./number-field";
export * from "./select-field";
export * from "./string-field";
export * from "./text-field";
export * from "./validatedFieldAtom";
2 changes: 0 additions & 2 deletions src/fields/multi-select-field/index.ts

This file was deleted.

21 changes: 0 additions & 21 deletions src/fields/multi-select-field/multiSelectField.ts

This file was deleted.

17 changes: 8 additions & 9 deletions src/fields/number-field/numberField.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { z } from "zod";
import { ExtractAtomValue } from "jotai";
import { ZodNumber, z } from "zod";

import {
ValidatedFieldAtom,
ValidatedFieldAtomConfig,
validatedFieldAtom,
} from "..";
import { ValidatedFieldAtomConfig, validatedFieldAtom } from "..";
import { ZodParams, defaultParams } from "../zodParams";

export type NumberFieldValue = number | undefined;
export type NumberFieldAtom = ReturnType<typeof numberField>;

export type NumberFieldAtom = ValidatedFieldAtom<NumberFieldValue>;
export type NumberFieldValue = ExtractAtomValue<
ExtractAtomValue<NumberFieldAtom>["value"]
>;

export const numberField = ({
required_error = defaultParams.required_error,
...config
}: Partial<ValidatedFieldAtomConfig<NumberFieldValue>> & ZodParams = {}) =>
}: Partial<ValidatedFieldAtomConfig<ZodNumber>> & ZodParams = {}) =>
validatedFieldAtom({
value: undefined,
schema: z.number({ required_error }),
Expand Down
3 changes: 0 additions & 3 deletions src/fields/select-field/index.ts

This file was deleted.

22 changes: 0 additions & 22 deletions src/fields/select-field/selectField.ts

This file was deleted.

File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions src/fields/string-field/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./stringField";
export * from "./useSelectFieldProps";
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ReactNode } from "react";

import { SelectFieldAtom, selectField } from "./selectField";
import { SelectFieldAtom, selectField } from "./stringField";
import { useSelectFieldProps } from "./useSelectFieldProps";
import { SelectOptionsProps, useSelectOptions } from "./useSelectOptions";
import {
SelectOptionsProps,
useSelectOptions,
} from "../../hooks/use-select-options/useSelectOptions";
import { FieldLabel } from "../../components";
import { FieldErrors } from "../../components/field-errors";
import { FormStory, fixArgs, meta } from "../../scenarios/StoryForm";
Expand Down
21 changes: 21 additions & 0 deletions src/fields/string-field/stringField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ExtractAtomValue } from "jotai";
import { ZodString, z } from "zod";

import { ValidatedFieldAtomConfig, validatedFieldAtom } from "..";
import { ZodParams, defaultParams } from "../zodParams";

export type StringFieldAtom = ReturnType<typeof stringField>;

export type StringFieldValue = ExtractAtomValue<
ExtractAtomValue<StringFieldAtom>["value"]
>;

export const stringField = ({
required_error = defaultParams.required_error,
...config
}: Partial<ValidatedFieldAtomConfig<ZodString>> & ZodParams = {}) =>
validatedFieldAtom({
value: undefined,
schema: z.string({ required_error }),
...config,
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { ChangeEvent } from "react";

import { SelectFieldAtom } from "./selectField";
import { StringFieldAtom } from "./stringField";
import { useFieldProps } from "../../hooks";

const getEventValue = (
event: ChangeEvent<HTMLSelectElement> | ChangeEvent<HTMLInputElement>
) => event.target.value || undefined;

export const useSelectFieldProps = (field: SelectFieldAtom) =>
// TODO: Coerce primitives & make generic
export const useSelectFieldProps = (field: StringFieldAtom) =>
useFieldProps<string | undefined, HTMLSelectElement | HTMLInputElement>(
field,
getEventValue
Expand Down
17 changes: 8 additions & 9 deletions src/fields/text-field/textField.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { z } from "zod";
import { ExtractAtomValue } from "jotai";
import { ZodString, z } from "zod";

import {
ValidatedFieldAtom,
ValidatedFieldAtomConfig,
validatedFieldAtom,
} from "..";
import { ValidatedFieldAtomConfig, validatedFieldAtom } from "..";
import { ZodParams, defaultParams } from "../zodParams";

export type TextFieldValue = string;
export type TextFieldAtom = ReturnType<typeof textField>;

export type TextFieldAtom = ValidatedFieldAtom<TextFieldValue>;
export type TextFieldValue = ExtractAtomValue<
ExtractAtomValue<TextFieldAtom>["value"]
>;

export const textField = ({
required_error = defaultParams.required_error,
...config
}: Partial<ValidatedFieldAtomConfig<TextFieldValue>> & ZodParams = {}) =>
}: Partial<ValidatedFieldAtomConfig<ZodString, ZodString>> & ZodParams = {}) =>
validatedFieldAtom({
value: "",
// https://github.com/colinhacks/zod/issues/63
Expand Down
Loading

0 comments on commit 378d1b5

Please sign in to comment.