一键导入
sif-formik-to-rhf
Migrer et skjema fra @navikt/sif-common-formik-ds (Formik) til @sif/rhf (React Hook Form). Bevarer oppførsel og data inn/ut.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Migrer et skjema fra @navikt/sif-common-formik-ds (Formik) til @sif/rhf (React Hook Form). Bevarer oppførsel og data inn/ut.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | sif-formik-to-rhf |
| type | action |
| description | Migrer et skjema fra @navikt/sif-common-formik-ds (Formik) til @sif/rhf (React Hook Form). Bevarer oppførsel og data inn/ut. |
formik til rhf, konverter skjema, bytt ut formik, migrer form, sif-common-formik-ds → sif/rhf.useForm, createSifFormComponents, SifFormFormFields-enum (behold eksisterende feltnavn)handleSubmit-logikk utover try/catch-wrapping (se fallgruver)sif-intl.sif-soknad-modify-step.Det finnes to strategier. Brukeren kan be om en spesifikk strategi. Hvis ikke spesifisert, velg Strategi B som standard — den gir færre mellomfeil og renere resultat.
Bytt Formik-kode med RHF-ekvivalenter linje for linje ved hjelp av erstatningstabellen.
Fase 1 — Les (før kode)
Les disse filene for skjemaet som skal migreres:
@navikt/sif-common-formik-ds)validation.*-nøkler)spørsmål/-mappen — les alle for å fange validatorparametere (minLength, maxLength, disallowedValues, disallowInvalidBackendCharacters)__tests__/ og .test.ts-filer) for stegets utilsFase 2 — Migrer
Utfør alle endringer. Bruk erstatningstabellen under.
Fase 3 — Verifiser
get_errors).@sif/rhf finnes i package.json → dependencies.Forstå hva skjemaet gjør først, skriv deretter ren RHF-kode fra scratch. Unngår å arve Formik-idiomer og gir færre mellomfeil.
Fase 1 — Analyser (les, ikke skriv)
Les skjema-filen, i18n-filer og package.json. Dokumenter følgende internt (ikke i koden):
nb.ts (f.eks. @ungInnsyn.inntektForm), inkl. alle valideringsnøkler. List opp alle mulige feilkoder per validator.Fase 2 — Reimplementer
Skriv hele komponenten på nytt basert på analysen, ikke basert på Formik-koden. Bruk RHF-mønsteret direkte:
useForm<FormValues> med defaultValues, mode: 'onSubmit', reValidateMode: 'onChange'createSifFormComponents<FormValues>() for feltkomponenteruseSifValidate(scope) med korrekt i18n-scope (inkl. namespace-prefiks)<SifForm> med buttons-propmethods.watch() for betinget visningtry/catch rundt async submitBehold: FormFields-enum, filnavn, mappestruktur, layout/JSX inni felter.
Fase 3 — Verifiser
get_errors).@sif/rhf finnes i package.json → dependencies.nb.ts og nn.ts.nb.ts/nn.ts skal være identiske med v1-kildene. Gjelder også interpolasjonsparametere ({land}, {navn}, {dato}, {min}, {maks}). Unntak kun ved strukturendringer — avvik skal begrunnes eksplisitt.extraValuesMapper i validateField sender alle parametere som teksten refererer til.__tests__/ og .test.ts-filer for utils-funksjoner som er med videre. Tilpass import (eksplisitt import { describe, expect, it, vi } from 'vitest' hvis appen ikke bruker globals).Formik (@navikt/sif-common-formik-ds) | RHF (@sif/rhf + react-hook-form) |
|---|---|
getTypedFormComponents<Fields, Values, ValidationError>() | createSifFormComponents<FormValues>() |
FormikWrapper initialValues={…} onSubmit={…} renderForm={({ values }) => …} | useForm<FormValues>({ defaultValues, mode: 'onSubmit', reValidateMode: 'onChange' }) + <SifForm methods={methods} onSubmit={handleSubmit}> |
<Form submitButtonLabel cancelButtonLabel submitPending onCancel includeValidationSummary formErrorHandler> | <SifForm buttons={…}> — repliker knapper med <Button type="submit" loading={isPending}> / <Button type="button" onClick={onCancel}> |
values.feltNavn (fra renderForm callback) | methods.watch(feltNavn) |
getIntlFormErrorHandler(intl, 'scope.validation') | useSifValidate('scope') → validateField(fieldName, validator) |
validate={getYesOrNoValidator()} (returnerer error-objekt { key, values }) | validate={validateField(fieldName, getYesOrNoValidator())} (returnerer streng) |
YesOrNo fra @navikt/sif-common-formik-ds | YesOrNo fra @sif/rhf |
ValidationError type | Ikke nødvendig — fjern |
getNumberFromNumberInputValue(value) | getNumberFromNumberInputValue fra @sif/rhf/utils — samme funksjon, ny import |
initialValues={data} (pre-filled fra API) | defaultValues: data — funksjonelt ekvivalent |
import {
getTypedFormComponents,
getIntlFormErrorHandler,
ValidationError,
YesOrNo,
} from '@navikt/sif-common-formik-ds';
import { createSifFormComponents, SifForm, useSifValidate, YesOrNo } from '@sif/rhf';
import { useForm } from 'react-hook-form';
Validatorer fra @navikt/sif-validation beholdes uendret.
Ikke importer fra gamle pakker i v2-apper. Bruk Aksel-komponenter eller @sif/*-pakker:
| Gammel komponent | Gammel pakke | Erstatning | Ny pakke |
|---|---|---|---|
ExpandableInfo | @navikt/sif-common-core-ds | ReadMore | @navikt/ds-react |
FormLayout | @navikt/sif-common-ui | FormLayout | @sif/soknad-ui/components |
For portering av *ListAndDialog-komponenter fra sif-common-forms-ds til sif-soknad-forms → bruk sif-dialog-migration.
useSifValidateFormik: getIntlFormErrorHandler(intl, 'myForm.validation') — scope inkluderer .validation.
RHF: useSifValidate('myForm') — scope ekskluderer .validation (legges til automatisk).
Begge produserer myForm.validation.fieldName.errorCode. Drop .validation-suffikset når du setter scope.
OBS — namespace-prefiks: Bruk full i18n-nøkkelprefiks inkludert eventuelt namespace. Hvis i18n-nøklene har prefiks @ungInnsyn.inntektForm.validation.*, må scope være '@ungInnsyn.inntektForm' — ikke 'inntektForm'. Se i nb.ts hva den faktiske prefiksen er.
Formik svelger rejected promises i onSubmit. RHF gjør det ikke — ubehandlede rejections kaster.
Regel: Hvis handleSubmit kaller await mutateAsync(…) eller andre async-operasjoner som kan feile, wrap i try/catch. La catchen være tom hvis mutation-hookets error-state håndterer visning.
Formik-validate returnerer { key, values } for feilmeldinger med parametere. RHF-validate returnerer en ren streng.
Bruk tredje argument til validateField — en callback som mottar feilkoden og returnerer interpolasjonsverdier:
validate={validateField(
FormFields.feltNavn,
getStringValidator({ required: true, minLength: 5, maxLength: 2000 }),
(errorCode) => {
if (errorCode === 'stringIsTooShort') return { min: 5 };
if (errorCode === 'stringIsTooLong') return { maks: 2000 };
},
)}
For statiske verdier som ikke avhenger av feilkoden kan du sende et vanlig objekt:
validate={validateField(FormFields.feltNavn, validator, { min: 5, maks: 2000 })}
Ikke bruk intl.formatMessage direkte — validateField bygger i18n-nøkkelen og gjør oppslaget for deg.
Bruk alltid gap="space-16" på <HStack> rundt submit/cancel-knapper — ikke space-4:
<HStack gap="space-16">
<Button type="submit" loading={isPending}>
…
</Button>
<Button variant="secondary" type="button" onClick={onCancel}>
…
</Button>
</HStack>
showButtonArrows og andre Form-props som forsvinnerFormik <Form> har props som showButtonArrows, includeValidationSummary og formErrorHandler som ikke har direkte ekvivalenter i <SifForm>. Disse droppes uten erstatning:
YesOrNo-typen i FormValuesBruker du YesOrNoQuestion fra @sif/rhf, skal feltet i FormValues typet som YesOrNo — ikke widen til string.
RHF støtter enum-typer direkte, og korrekt typing fjerner behovet for as YesOrNo-caster i konverteringsfunksjonene.
// Feil:
type MyFormValues = {
harSamtykket: string; // ← widened unødvendig
};
const harSamtykket = values.harSamtykket as YesOrNo; // ← cast unødvendig
// Riktig:
type MyFormValues = {
harSamtykket: YesOrNo;
};
const harSamtykket = values.harSamtykket; // ← ingen cast nødvendig
showButtonArrows — finnes ikke i RHF, drop.includeValidationSummary — SifForm inkluderer SifValidationSummary automatisk.formErrorHandler — erstattes av useSifValidate (se fallgruve 1).// Før: values[FormFields.felt] === YesOrNo.YES (fra renderForm callback)
// Etter:
const feltValue = methods.watch(FormFields.felt);
// I JSX:
{feltValue === YesOrNo.YES ? <Textarea ... /> : null}
Den vanligste feilen ved migrering er invertert betingelseslogikk — man skriver === NO der v1 hadde === YES eller omvendt. Kartlegg eksplisitt betingelsene fra v1 FØR koding:
felt A → alltid synlig
felt B → synlig når felt A === YES ← les nøye, ikke inverter
melding X → synlig når felt A === NO
Sjekk tabellen:
| v1-kode | Feil v2-kode | Riktig v2-kode |
|---|---|---|
kronisk === YES | kronisk === NO | kronisk === YES |
harBarn === false | harBarn === true | harBarn === false |
&&, ikke AriaLiveRegionAriaLiveRegion skal kun brukes rundt dynamiske meldinger som skal annonseres til skjermlesere. Bruk ikke AriaLiveRegion rundt spørsmål/skjemafelter — bruk && i stedet:
// Skjemafelter — bruk &&
{feltVerdi === YesOrNo.YES && <NyttFelt ... />}
// Gruppe av felter — bruk fragment
{feltVerdi === YesOrNo.YES && (
<>
<FeltA ... />
<FeltB ... />
</>
)}
// Melding som skal annonseres til skjermlesere — bruk AriaLiveRegion
<AriaLiveRegion visible={feltVerdi === YesOrNo.NO}>
<QuestionRelatedMessage>
<SifInfoCard>...</SifInfoCard>
</QuestionRelatedMessage>
</AriaLiveRegion>
FormLayout.Questions er ikke nødvendig rundt betinget innhold — gapet styres av den ytre FormLayout.Questions.
Når felter skjules (unmountes) beholdes feilmeldinger i RHF's form state (RHF bruker shouldUnregister: false som default). Rydd opp med clearErrors i en useEffect som kjører når betingelsen som skjuler feltet endrer seg.
Prinsipp: dependency-arrayet skal inneholde det uttrykket som avgjør om feltet er synlig — enten det er en enkel verdi, en beregnet boolean, eller en kombinasjon.
// Enkel verdi som dependency
useEffect(() => {
if (triggerFelt !== YesOrNo.YES) {
methods.clearErrors([FormFields.betingetFelt]);
}
}, [triggerFelt]);
// Beregnet synlighetsuttrykk som dependency
const feltErSynlig = betingelse1 && betingelse2;
useEffect(() => {
if (!feltErSynlig) {
methods.clearErrors([FormFields.betingetFelt]);
}
}, [feltErSynlig]);
Velg den varianten som passer skjemaets logikk. Det viktige er at clearErrors kalles for alle felter som forsvinner fra skjermen, uavhengig av hva som utløste skjulingen.
Valideringsnøkler må matche <scope>.validation.<fieldName>.<errorCode>.
Bruk alltid de eksakte feilkodene fra validatorens enum — ikke dikk opp egne. Feil kode → valideringsmeldingen vises aldri.
| Validator | Faktiske feilkoder |
|---|---|
getYesOrNoValidator | yesOrNoIsUnanswered |
getRequiredFieldValidator | noValue |
getStringValidator | stringHasNoValue, stringIsTooShort, stringIsTooLong |
getCheckedValidator | notChecked |
getListValidator | listIsEmpty, listHasTooFewItems, listHasTooManyItems |
getDateValidator | dateHasNoValue, dateHasInvalidFormat, dateIsBeforeMin, dateIsAfterMax |
getFødselsnummerValidator | fødselsnummerHasNoValue, fødselsnummerIsNot11Chars, fødselsnummerIsInvalid, fødselsnummerAsHnrIsNotAllowed |
Finn alle koder i packages/sif-validation/src/get*Validator.ts via enum Validate*Error.
Sjekk: Felt-enum i Formik og felt-navnene i i18n-nøklene må matche. Hvis formik brukte formErrorHandler med scope 'uttalelseForm.validation' men felt-enum heter harUttalelse, må nøkkelen i nb.ts være:
'uttalelseForm.validation.harUttalelse.yesOrNoIsUnanswered': '...'
Hvis eksisterende nøkler bruker et annet feltnavn enn FormFields-enum, oppdater nøklene til å matche enum-verdien.
Legg til @sif/rhf i dependencies for workspacen:
"@sif/rhf": "workspace:*"
Kjør pnpm install etterpå for å lenke pakken.
Noen filer importerer kun DateRange-typen fra @navikt/sif-common-formik-ds. Denne typen finnes også i @navikt/sif-common-utils. Bytt import hvis det er eneste formik-import:
// Før
import { DateRange } from '@navikt/sif-common-formik-ds';
// Etter
import { DateRange } from '@navikt/sif-common-utils';
Dato-APIet er delt i to lag:
| Lag | Pakke | Eksport | Bruksområde |
|---|---|---|---|
| Datepicker-parsing | @sif/rhf → datePickerUtils | parseDatePickerValue(value), getDisabledDates(...) | Konverterer brukerinput fra datepicker (ISO-streng eller norske datoformater) til Date | undefined |
| Generell ISO-konvertering | @navikt/sif-common-utils → dateUtils | dateToISODate(date), ISODateToDate(iso), isISODateString(value) | Konvertering mellom Date og ISODate-strenger |
Regler for mapping-funksjoner i steg-utils:
toSøknadsdata / formValuesToXxx: Bruk datePickerUtils.parseDatePickerValue(value) for å parse datepicker-verdier til Date.toFormValues / xxxToFormValues: Bruk dateUtils.dateToISODate(date) for å konvertere Date til ISO-streng.dateUtils.isISODateString(value).new Date(...) skal ikke brukes for datoparsing i runtime-kode. ISODateToDate er greit i tester/mock.@navikt/sif-common-formik-ds-importer er fjernet fra skjemafilen@sif/rhf og react-hook-form brukesFormikWrapper → useForm + SifFormForm → SifForm med buttons-prop (repliker submit/cancel-oppførsel)useSifValidate-scope er uten .validation-suffikshandleSubmit har try/catch rundt mutateAsync-kallmethods.watch() + &&/fragment — ikke AriaLiveRegion rundt skjemafelternb.ts/nn.ts er identiske med v1 — inkl. interpolasjonsparametereextraValuesMapper sender alle parametere teksten refererer til ({land}, {navn}, {dato} etc.)sif-soknad-formsDialogkomponenter i sif-soknad-forms skal som hovedregel bruke useSifValidate, også når scope ligger på pakkenivå.
Bruk scope med full pakke-prefix, for eksempel @sifSoknadForms.bostedUtlandForm.
Hvis originalen følger standardmønsteret <scope>.validation.<fieldName>.<errorCode>, holder det med:
const { validateField } = useSifValidate('@sifSoknadForms.dialogForm');
validate: validateField(FormFields.landkode, getRequiredFieldValidator());
Hvis originalen trenger interpolasjonsverdier, bruk fortsatt useSifValidate, men send inn values.
Hvis originalen ikke følger standardmønsteret <scope>.validation.<fieldName>.<errorCode>, må du vurdere om useSifValidate fortsatt passer. Ikke innfør ekstra kompleksitet uten et faktisk behov.
useSifValidateNår originalen trenger interpolasjonsverdier, send dem inn som tredje argument til validateField:
validate: validateField(
FormFields.fom,
getDateValidator({ required: true, min: minDate, max: maxDate }),
(errorCode) => {
if (errorCode === 'dateIsBeforeMin' && minDate)
return { dato: sifIntl.date(minDate, 'compact') };
if (errorCode === 'dateIsAfterMax' && maxDate)
return { dato: sifIntl.date(maxDate, 'compact') };
},
),
Formik-versjonen bruker getDateRangeValidator med manuell beregning av resolvedMaxDate/resolvedMinDate per felt. I RHF-versjonen er dette unødvendig — SifDateRangePicker håndterer cross-field min/max automatisk.
Mønster:
getDateValidator (ikke getDateRangeValidator) per felt — hvert felt sjekker bare seg selvSifDateRangePicker klemmer automatisk fom sin maxDate ned til tom-verdien, og omvendtDateRangePicker.validate-propvalidateField brukes også på gruppenivå for i18n-oppslag<DateRangePicker
name="periode"
legend={...}
validate={validateField('periode', ({ fromDate, toDate }) => {
if (fromDate && toDate && fromDate > toDate) return 'fromDateIsAfterToDate';
})}
fromInputProps={{
name: FormFields.fom,
validate: validateField(
FormFields.fom,
getDateValidator({ required: true, min: minDate, max: maxDate }),
(errorCode) => {
if (errorCode === 'dateIsBeforeMin' && minDate)
return { dato: sifIntl.date(minDate, 'compact') };
if (errorCode === 'dateIsAfterMax' && maxDate)
return { dato: sifIntl.date(maxDate, 'compact') };
},
),
}}
toInputProps={{
name: FormFields.tom,
validate: validateField(
FormFields.tom,
getDateValidator({ required: true, min: minDate, max: maxDate }),
(errorCode) => {
if (errorCode === 'dateIsBeforeMin' && minDate)
return { dato: sifIntl.date(minDate, 'compact') };
if (errorCode === 'dateIsAfterMax' && maxDate)
return { dato: sifIntl.date(maxDate, 'compact') };
},
),
}}
/>
i18n-nøkler for cross-field-feilen bruker gruppens navn som felt: scope.validation.periode.fromDateIsAfterToDate.
nb.ts og nn.tsnn.ts er typet med Record<keyof typeof nb, string> — ingen spread fra nbsif-common-forms-ds — ikke omskrevetAriaLiveRegion brukes kun rundt dynamiske meldinger (SifInfoCard, InlineMessage, LocalAlert)clearErrors i useEffectuseSifValidate brukes når key-strukturen følger standardmønsteret, med values ved behov@navikt/sif-validation (sjekk enum Validate*Error)FormFields-enum er uendret@sif/rhf: workspace:* finnes i package.jsonDateRange-importer er oppdatert (hvis aktuelt)*StegUtils.ts (se under)sif-dialog-migration → Innholdsverifisering)*StegUtils.tsMapping-funksjoner (toFormValues og toSøknadsdata) er rene funksjoner og enkle å teste. De dekker nøyaktig der inversjons- og mappingfeil oppstår — feil som ikke fanges av kompilator.
Viktig: La ikke AI skrive assertions basert på v2-koden — da verifiseres bare AI-ens egen (potensielt feil) forståelse. Bruk v1-koden som sannhetskilde:
getOmBarnetSøknadsdataFromFormValues (eller tilsvarende) i kildeappenAlternativt: utvikler skriver assertions basert på domenekunnskap — ikke AI.
Opprett __tests__/<prefix>StegUtils.test.ts ved siden av utils-filen. Dekk minst:
undefined når betingelsen ikke er oppfylttoSøknadsdata → toFormValues gir tilbake originale verdierimport { YesOrNo } from '@sif/rhf';
import { toOmBarnetSøknadsdata, toOmBarnetFormValues } from '../omBarnetStegUtils';
describe('toOmBarnetSøknadsdata', () => {
it('lagrer høyereRisikoForFravær når kronisk er YES', () => {
const result = toOmBarnetSøknadsdata({ kroniskEllerFunksjonshemming: YesOrNo.YES, høyereRisikoForFravær: YesOrNo.YES, ... }, registrerteBarn);
expect(result?.høyereRisikoForFravær).toBe(true);
});
it('høyereRisikoForFravær er undefined når kronisk er NO', () => {
const result = toOmBarnetSøknadsdata({ kroniskEllerFunksjonshemming: YesOrNo.NO, ... }, registrerteBarn);
expect(result?.høyereRisikoForFravær).toBeUndefined();
});
});
Mønster for å kombinere flere API-hooks til én asynkron laster (useInitialData + InitialDataLoader) i sif-soknad-apper.
Legg til et nytt steg i en søknadsapp som bruker @sif/soknad og @sif/rhf — oppretter alle filer og oppdaterer config/routing/i18n.
Arkitekturreferanse og beslutningslogg for @sif/soknad-app — rammeverket for søknadsapper i sif-brukerdialog.
Sett opp OppsummeringSteg i en v2-app — DTO-basert oppsummering med FormSummary, bekreftelsescheckbox og i18n-nøkler.
Veiledning for oppsett av en ny søknadsapp med @sif/soknad-app — SøknadRouter, SøknadAppProvider, AppContext og routing shell.
Implementer et vedleggssteg i en v2-app — VedleggPanel, PersistedVedlegg, hydration og MSW-handlere.