diff --git a/src/useForm.ts b/src/useForm.ts index f69d927..d485fe0 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -233,7 +233,11 @@ export const useForm = , ErrorMessage = string>( names.forEach((name) => fields.current[name].callbacks.add(callback)); return () => { - names.forEach((name) => fields.current[name].callbacks.delete(callback)); + names.forEach((name) => { + if (fields.current[name] != null) { + fields.current[name].callbacks.delete(callback); + } + }); }; }; @@ -399,7 +403,9 @@ export const useForm = , ErrorMessage = string>( fields.current[name].callbacks.add(callback); return () => { - fields.current[name].callbacks.delete(callback); + if (fields.current[name] != null) { + fields.current[name].callbacks.delete(callback); + } }; }, }), @@ -423,7 +429,9 @@ export const useForm = , ErrorMessage = string>( return () => { if (isFirstMounting) { - fields.current[name].mounted = false; + if (fields.current[name] != null) { + fields.current[name].mounted = false; + } } }; }, [name]); diff --git a/website/src/App.tsx b/website/src/App.tsx index 720b7d1..877e9ca 100644 --- a/website/src/App.tsx +++ b/website/src/App.tsx @@ -11,6 +11,7 @@ import { AsyncSubmissionForm } from "./forms/AsyncSubmissionForm"; import { BasicForm } from "./forms/BasicForm"; import { CheckboxesForm } from "./forms/CheckboxesForm"; import { CreditCardForm } from "./forms/CreditCardForm"; +import { Dynamic } from "./forms/Dynamic"; import { FieldsListenerForm } from "./forms/FieldsListenerForm"; import { IBANForm } from "./forms/IBANForm"; import { InputMaskingForm } from "./forms/InputMaskingForm"; @@ -76,6 +77,7 @@ export const App = () => { IBAN Credit card Input masking + Dynamic fields )} @@ -89,6 +91,7 @@ export const App = () => { .with({ name: "IBAN" }, () => ) .with({ name: "CreditCard" }, () => ) .with({ name: "InputMasking" }, () => ) + .with({ name: "Dynamic" }, () => ) .with(P.nullish, () => ) .exhaustive()} diff --git a/website/src/forms/Dynamic.tsx b/website/src/forms/Dynamic.tsx new file mode 100644 index 0000000..7542f92 --- /dev/null +++ b/website/src/forms/Dynamic.tsx @@ -0,0 +1,131 @@ +import { Button } from "@chakra-ui/button"; +import { HStack, Spacer } from "@chakra-ui/layout"; +import { useToast } from "@chakra-ui/toast"; +import { useForm } from "@swan-io/use-form"; +import * as React from "react"; +import { Input } from "../components/Input"; +import { Page } from "../components/Page"; + +export const Dynamic = () => { + const [fields, setFields] = React.useState<{ key: string; name: string }[]>([]); + + const config = React.useMemo( + () => + Object.fromEntries( + fields.map((item) => [ + item.key, + { + strategy: "onBlur" as const, + initialValue: "", + sanitize: (value: string) => value.trim(), + validate: (value: string) => { + if (value === "") { + return "First name is required"; + } + }, + }, + ]), + ), + [fields], + ); + + const { Field, resetForm, submitForm } = useForm(config); + + const toast = useToast(); + + const onSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + submitForm({ + onSuccess: (values) => { + console.log("values", values); + + toast({ + title: "Submission succeeded", + status: "success", + duration: 5000, + isClosable: true, + }); + }, + onFailure: (errors) => { + console.log("errors", errors); + + toast({ + title: "Submission failed", + status: "error", + duration: 5000, + isClosable: true, + }); + }, + }); + }; + + return ( + + A common form example which play with at least two different strategies. +
+ Note that all values are sanitized using trimming. + + } + > + + + + + + + +
{ + resetForm(); + }} + > + {fields.map((field) => ( + + {({ error, onBlur, onChange, ref, valid, value }) => ( + + )} + + ))} + + + + + + + + + +
+ ); +}; diff --git a/website/src/utils/router.ts b/website/src/utils/router.ts index 8fac7e0..ee437e2 100644 --- a/website/src/utils/router.ts +++ b/website/src/utils/router.ts @@ -10,6 +10,7 @@ const routesObject = { IBAN: "/iban", CreditCard: "/credit-card", InputMasking: "/input-masking", + Dynamic: "/dynamic", } as const; export const routes = Dict.keys(routesObject); diff --git a/website/yarn.lock b/website/yarn.lock index a76be25..4e124ac 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -1168,10 +1168,10 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz#0bb7ac3cd1c3292db1f39afdabfd03ccea3a3d34" integrity sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag== -"@swan-io/boxed@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@swan-io/boxed/-/boxed-2.1.1.tgz#bdf17d725270c8560d2371972427b6f7e80da5d8" - integrity sha512-NzmIVAnJuzuPwm8ZR4I9tUpuo9qjtVNA7aTSZYdSvcJ+y7NTyt9+tCi4y87T3m8hACN2ATMqvTmRtHOSDIIz7w== +"@swan-io/boxed@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@swan-io/boxed/-/boxed-2.3.0.tgz#aafa19432a3fa2c00d472be7832632619c4ed5ff" + integrity sha512-ubonCw2GLblwsA9rodcDnhLvCY+0d9g0KyMFyzKZa+vPumWQ8IZ6HFjdQT3Nm5yC+/RnSmJ20RMPwOn/06M8Vw== "@swan-io/chicane@^2.0.0": version "2.0.0"