Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dynamic config #32

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,11 @@ export const useForm = <Values extends Required<Values>, 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);
}
});
};
};

Expand Down Expand Up @@ -399,7 +403,9 @@ export const useForm = <Values extends Required<Values>, 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);
}
};
},
}),
Expand All @@ -423,7 +429,9 @@ export const useForm = <Values extends Required<Values>, ErrorMessage = string>(

return () => {
if (isFirstMounting) {
fields.current[name].mounted = false;
if (fields.current[name] != null) {
fields.current[name].mounted = false;
}
}
};
}, [name]);
Expand Down
3 changes: 3 additions & 0 deletions website/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -76,6 +77,7 @@ export const App = () => {
<Link to={Router.IBAN()}>IBAN</Link>
<Link to={Router.CreditCard()}>Credit card</Link>
<Link to={Router.InputMasking()}>Input masking</Link>
<Link to={Router.Dynamic()}>Dynamic fields</Link>
</VStack>
</Flex>
)}
Expand All @@ -89,6 +91,7 @@ export const App = () => {
.with({ name: "IBAN" }, () => <IBANForm />)
.with({ name: "CreditCard" }, () => <CreditCardForm />)
.with({ name: "InputMasking" }, () => <InputMaskingForm />)
.with({ name: "Dynamic" }, () => <Dynamic />)
.with(P.nullish, () => <Page title="Not found" />)
.exhaustive()}
</Flex>
Expand Down
131 changes: 131 additions & 0 deletions website/src/forms/Dynamic.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Page
title="Basic"
description={
<>
A common form example which play with at least two different strategies.
<br />
Note that all values are sanitized using trimming.
</>
}
>
<HStack justifyContent="flex-start" spacing={3}>
<Button
width={100}
onClick={() =>
setFields((fields) => [
...fields,
{ key: crypto.randomUUID(), name: `Field ${fields.length}` },
])
}
>
Add field
</Button>
<Button width={140} onClick={() => setFields((fields) => fields.slice(0, -1))}>
Remove field
</Button>
</HStack>

<Spacer height={8} />

<form
onSubmit={onSubmit}
onReset={() => {
resetForm();
}}
>
{fields.map((field) => (
<Field name={field.key} key={field.key}>
{({ error, onBlur, onChange, ref, valid, value }) => (
<Input
label={field.name}
validation="Required"
strategy="onBlur"
error={error}
onBlur={onBlur}
onChangeText={onChange}
ref={ref}
valid={valid}
value={value}
/>
)}
</Field>
))}

<Spacer height={4} />

<HStack justifyContent="flex-end" spacing={3}>
<Button type="reset" width={100}>
Reset
</Button>

<Button colorScheme="green" type="submit" width={100}>
Submit
</Button>
</HStack>
</form>
</Page>
);
};
1 change: 1 addition & 0 deletions website/src/utils/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const routesObject = {
IBAN: "/iban",
CreditCard: "/credit-card",
InputMasking: "/input-masking",
Dynamic: "/dynamic",
} as const;

export const routes = Dict.keys(routesObject);
Expand Down
8 changes: 4 additions & 4 deletions website/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading