From 126b454f379b520732c1065bbf5d69b1e90838c5 Mon Sep 17 00:00:00 2001 From: MiroslavPetrik Date: Thu, 18 Jan 2024 16:00:12 +0100 Subject: [PATCH] fix(useSelectFieldProps): make field initializable via options (#91) --- src/components/select/Select.stories.tsx | 25 ++++++++++ src/components/select/Select.test.tsx | 49 +++++++++++++------ src/components/select/Select.tsx | 26 +++++----- src/components/select/SelectField.mock.tsx | 6 +-- .../useSelectFieldProps.test.tsx | 14 ++++++ .../useSelectFieldProps.ts | 20 +++++--- .../use-select-options/useSelectOptions.tsx | 4 +- 7 files changed, 104 insertions(+), 40 deletions(-) diff --git a/src/components/select/Select.stories.tsx b/src/components/select/Select.stories.tsx index bc95dbd..7c45075 100644 --- a/src/components/select/Select.stories.tsx +++ b/src/components/select/Select.stories.tsx @@ -52,6 +52,31 @@ export const OptionalString = formStory({ }, }); +export const InitializedString = formStory({ + parameters: { + docs: { + description: { + story: "Field is initialized via the `initialValue` prop.", + }, + }, + }, + args: { + fields: { + country: stringField(), + }, + children: ({ fields }) => ( + key} + getLabel={({ name }) => name} + /> + ), + }, +}); + const ratingOptions = [5, 4, 3, 2, 1]; export const RequiredNumber = formStory({ diff --git a/src/components/select/Select.test.tsx b/src/components/select/Select.test.tsx index a97cce9..8ed5127 100644 --- a/src/components/select/Select.test.tsx +++ b/src/components/select/Select.test.tsx @@ -1,6 +1,11 @@ import { act, render, renderHook, screen } from "@testing-library/react"; import { userEvent } from "@testing-library/user-event"; -import { formAtom, useFormActions, useFormSubmit } from "form-atoms"; +import { + formAtom, + useFieldValue, + useFormActions, + useFormSubmit, +} from "form-atoms"; import { describe, expect, it, vi } from "vitest"; import { z } from "zod"; @@ -132,7 +137,7 @@ describe("", () => { getValue: (user: User) => user, }; - const form = formAtom({ field: props.field }); - const { result } = renderHook(() => useFormActions(form)); - render(); - await act(() => - userEvent.selectOptions(screen.getByRole("combobox"), [ - screen.getByText("boo 2"), - ]), - ); + await act(() => + userEvent.selectOptions(screen.getByRole("combobox"), [ + screen.getByText("boo 2"), + ]), + ); - const onSubmit = vi.fn(); - await act(async () => { - result.current.submit(onSubmit)(); + const onSubmit = vi.fn(); + await act(async () => { + result.current.submit(onSubmit)(); + }); + + expect(onSubmit).toHaveBeenCalledWith({ field: options[1] }); }); - expect(onSubmit).toHaveBeenCalledWith({ field: options[1] }); + it.skip("initializes via initialValue prop", async () => { + const field = zodField({ + value: undefined, + schema, + }); + + render( diff --git a/src/hooks/use-select-field-props/useSelectFieldProps.test.tsx b/src/hooks/use-select-field-props/useSelectFieldProps.test.tsx index 289dd36..4baf378 100644 --- a/src/hooks/use-select-field-props/useSelectFieldProps.test.tsx +++ b/src/hooks/use-select-field-props/useSelectFieldProps.test.tsx @@ -12,6 +12,20 @@ describe("useSelectFieldProps()", () => { getValue: (val: string) => val, }; + it("initializes field via options", () => { + const field = stringField(); + const index = 2; + + const { result } = renderHook(() => + useSelectFieldProps( + { field, ...props }, + { initialValue: props.options[index] }, + ), + ); + + expect(result.current.value).toBe(index); + }); + describe("initial value", () => { it("is -1 when field is empty", () => { const field = stringField(); diff --git a/src/hooks/use-select-field-props/useSelectFieldProps.ts b/src/hooks/use-select-field-props/useSelectFieldProps.ts index f27ea6e..62c3f06 100644 --- a/src/hooks/use-select-field-props/useSelectFieldProps.ts +++ b/src/hooks/use-select-field-props/useSelectFieldProps.ts @@ -1,14 +1,20 @@ +import { UseFieldOptions } from "form-atoms"; import { useAtomValue } from "jotai"; import { ChangeEvent, useCallback, useMemo } from "react"; -import { UseOptionsProps, useFieldProps } from ".."; -import { ZodField, ZodFieldValue } from "../../fields"; +import { FieldProps, type UseOptionsProps, useFieldProps } from ".."; +import type { ZodField, ZodFieldValue } from "../../fields"; /** * This restricts ZodField to have optional schema defaulting to 'undefined of required schema'. */ export type SelectField = ZodField; +export type SelectFieldProps< + Option, + Field extends SelectField, +> = UseSelectFieldProps & FieldProps; + export type UseSelectFieldProps = { field: Field; getValue: (option: Option) => NonNullable>; @@ -19,11 +25,10 @@ export type UseSelectFieldProps = { */ export const EMPTY_SELECT_VALUE = -1; -export const useSelectFieldProps = ({ - field, - options, - getValue, -}: UseSelectFieldProps) => { +export const useSelectFieldProps = ( + { field, options, getValue }: UseSelectFieldProps, + fieldOptions?: UseFieldOptions>, +) => { const atom = useAtomValue(field); const fieldValue = useAtomValue(atom.value); // TODO: getValue should be useMemo dependency, currently we asume that it is stable @@ -44,6 +49,7 @@ export const useSelectFieldProps = ({ const props = useFieldProps( field, getEventValue, + fieldOptions, ); return { ...props, value }; diff --git a/src/hooks/use-select-options/useSelectOptions.tsx b/src/hooks/use-select-options/useSelectOptions.tsx index 3ed3a85..5441450 100644 --- a/src/hooks/use-select-options/useSelectOptions.tsx +++ b/src/hooks/use-select-options/useSelectOptions.tsx @@ -2,9 +2,7 @@ import { useMemo } from "react"; import { UseOptionsProps, useOptions } from "../use-options"; -export type UseSelectOptionsProps