Skip to content

Commit

Permalink
Form validation examples (#3100)
Browse files Browse the repository at this point in the history
  • Loading branch information
HalvorHaugan authored Sep 3, 2024
1 parent 531e720 commit 052750b
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 0 deletions.
1 change: 1 addition & 0 deletions aksel.nav.no/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"prism-react-renderer": "^2.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-hook-form": "^7.53.0",
"react-markdown": "^9.0.1",
"react-responsive-masonry": "2.2.0",
"react-rx": "^2.1.3",
Expand Down
174 changes: 174 additions & 0 deletions aksel.nav.no/website/pages/templates/skjemavalidering/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React, { useEffect } from "react";
import { ArrowLeftIcon, ArrowRightIcon } from "@navikt/aksel-icons";
import {
Button,
ErrorSummary,
HGrid,
Heading,
Page,
Radio,
RadioGroup,
TextField,
VStack,
} from "@navikt/ds-react";

const validatePersonnummer = (p: string) => {
// Det er anbefalt å bruke https://github.com/navikt/fnrvalidator for å validere personnummer.
if (p.length === 0) {
return "Du må fylle ut personnummer.";
}
if (!/^\d{11}$/.test(p)) {
return "Personnummer må være 11 siffer.";
}
return "";
};

const Example = () => {
const errorSummaryRef = React.useRef<HTMLDivElement>(null);

const [formState, setFormState] = React.useState({
submitted: false,
tries: 0,
});
const [values, setValues] = React.useState({
personnummer: "",
transportmiddel: "",
});
const [errors, setErrors] = React.useState({
personnummer: "",
transportmiddel: "",
});

function onSubmit(event: React.FormEvent) {
event.preventDefault();

const newErrors = {
personnummer: validatePersonnummer(values.personnummer),
transportmiddel: values.transportmiddel
? ""
: "Du må velge et transportmiddel.",
};
setErrors(newErrors);

if (Object.values(newErrors).some(Boolean)) {
setFormState({ ...formState, tries: formState.tries + 1 });
} else {
setFormState({ ...formState, submitted: true });
}
}

useEffect(() => {
errorSummaryRef.current?.focus();
}, [formState.tries]);

if (formState.submitted)
return (
<Page>
<Page.Block as="main" width="lg" gutters>
<VStack gap="8" align="center">
<Heading size="large">Demo slutt</Heading>
<Button
onClick={() => {
location.hash = "";
location.reload();
}}
>
Nullstill
</Button>
</VStack>
</Page.Block>
</Page>
);

return (
<Page>
<Page.Block as="main" width="lg" gutters>
<form onSubmit={onSubmit}>
<VStack gap="8">
<TextField
id="personnummer"
label="Personnummer"
value={values.personnummer}
onChange={(e) =>
setValues({ ...values, personnummer: e.currentTarget.value })
}
onBlur={() => {
formState.tries &&
setErrors({
...errors,
personnummer: validatePersonnummer(values.personnummer),
});
}}
error={errors.personnummer}
/>
<RadioGroup
id="transportmiddel"
tabIndex={-1}
legend="Transportmiddel"
value={values.transportmiddel}
onChange={(newValue) => {
setValues({ ...values, transportmiddel: newValue });
setErrors({ ...errors, transportmiddel: "" });
}}
error={errors.transportmiddel}
>
<Radio value="car">Bil</Radio>
<Radio value="walking">Gange</Radio>
<Radio value="public">Kollektivtransport</Radio>
</RadioGroup>

{Object.values(errors).some(Boolean) && (
<ErrorSummary
ref={errorSummaryRef}
heading="Du må rette disse feilene før du kan fortsette:"
>
{Object.entries(errors)
.filter(([, error]) => error)
.map(([key, error]) => (
<ErrorSummary.Item href={`#${key}`} key={key}>
{error}
</ErrorSummary.Item>
))}
</ErrorSummary>
)}

<HGrid
gap={{ xs: "4", sm: "8 4" }}
columns={{ xs: 1, sm: 2 }}
width={{ sm: "fit-content" }}
>
<Button
type="button"
variant="secondary"
icon={<ArrowLeftIcon aria-hidden />}
iconPosition="left"
>
Forrige steg
</Button>
<Button
type="submit"
variant="primary"
icon={<ArrowRightIcon aria-hidden />}
iconPosition="right"
>
Neste steg
</Button>
</HGrid>
</VStack>
</form>
</Page.Block>
</Page>
);
};

// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default Example;

/* Storybook story */
export const Demo = {
render: Example,
};

export const args = {
index: 1,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { ArrowLeftIcon, ArrowRightIcon } from "@navikt/aksel-icons";
import {
Button,
ErrorSummary,
HGrid,
Heading,
Page,
Radio,
RadioGroup,
TextField,
VStack,
} from "@navikt/ds-react";

type Inputs = {
personnummer: string;
transportmiddel: string;
};

const Example = () => {
const errorSummaryRef = React.useRef<HTMLDivElement>(null);

const {
register,
handleSubmit,
trigger,
formState: { isSubmitSuccessful, errors },
} = useForm<Inputs>({
reValidateMode: "onBlur",
shouldFocusError: false,
});

const onValidSubmit: SubmitHandler<Inputs> = (data) => {
console.info("data", data);
};

if (isSubmitSuccessful)
return (
<Page>
<Page.Block as="main" width="lg" gutters>
<VStack gap="8" align="center">
<Heading size="large">Demo slutt</Heading>
<Button
onClick={() => {
location.hash = "";
location.reload();
}}
>
Nullstill
</Button>
</VStack>
</Page.Block>
</Page>
);

return (
<Page>
<Page.Block as="main" width="lg" gutters>
<form
onSubmit={(event) => {
handleSubmit(onValidSubmit)(event).then(() => {
errorSummaryRef.current?.focus();
});
}}
>
<VStack gap="8">
<TextField
id="personnummer"
label="Personnummer"
error={errors.personnummer?.message}
{...register("personnummer", {
required: "Du må fylle ut personnummer.",
pattern: {
value: /^\d{11}$/,
message: "Personnummer må være 11 siffer.",
}, // Det er anbefalt å bruke https://github.com/navikt/fnrvalidator for å validere personnummer.
})}
/>
<RadioGroup
id="transportmiddel"
tabIndex={-1}
legend="Transportmiddel"
error={errors.transportmiddel?.message}
onChange={() => trigger("transportmiddel")}
name="transportmiddel"
>
{["Bil", "Gange", "Kollektivtransport"].map((value) => (
<Radio
key={value}
value={value}
{...register("transportmiddel", {
required: "Du må velge et transportmiddel.",
})}
>
{value}
</Radio>
))}
</RadioGroup>

{Object.values(errors).length > 0 && (
<ErrorSummary
ref={errorSummaryRef}
heading="Du må rette disse feilene før du kan fortsette:"
>
{Object.entries(errors).map(([key, error]) => (
<ErrorSummary.Item key={key} href={`#${key}`}>
{error.message}
</ErrorSummary.Item>
))}
</ErrorSummary>
)}

<HGrid
gap={{ xs: "4", sm: "8 4" }}
columns={{ xs: 1, sm: 2 }}
width={{ sm: "fit-content" }}
>
<Button
type="button"
variant="secondary"
icon={<ArrowLeftIcon aria-hidden />}
iconPosition="left"
>
Forrige steg
</Button>
<Button
type="submit"
variant="primary"
icon={<ArrowRightIcon aria-hidden />}
iconPosition="right"
>
Neste steg
</Button>
</HGrid>
</VStack>
</form>
</Page.Block>
</Page>
);
};

// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default Example;

/* Storybook story */
export const Demo = {
render: Example,
};

export const args = {
index: 2,
};
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21847,6 +21847,15 @@ __metadata:
languageName: node
linkType: hard

"react-hook-form@npm:^7.53.0":
version: 7.53.0
resolution: "react-hook-form@npm:7.53.0"
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
checksum: 10/b7d73696b7c10e042f6ea6fcec01f951091146bfbc89d1378327a970bcd724b968e93fae1657bddada75caf648cfaf8693c5ba03c25e96816b755079d29f65da
languageName: node
linkType: hard

"react-i18next@npm:14.0.2":
version: 14.0.2
resolution: "react-i18next@npm:14.0.2"
Expand Down Expand Up @@ -27392,6 +27401,7 @@ __metadata:
prism-react-renderer: "npm:^2.0.0"
react: "npm:^18.0.0"
react-dom: "npm:^18.0.0"
react-hook-form: "npm:^7.53.0"
react-markdown: "npm:^9.0.1"
react-responsive-masonry: "npm:2.2.0"
react-rx: "npm:^2.1.3"
Expand Down

0 comments on commit 052750b

Please sign in to comment.