Utfordringen: Dynamisk skjemaarkitektur for offentlig sektor Norske kommuner administrerer hundrevis av forskjellige tilskuddsordninger, hver med unike søknadskrav. Å bygge et separat skjema for hvert program var ikke skalerbart. Løsningen måtte være en 'skjemabygger' som gjorde det mulig for offentlig ansatte å konstruere søknadsskjemaer dynamisk uten å skrive kode.
En teknisk utfordring var å skape et hybrid UI som håndterte både statiske systemfelter (søkerens navn, organisasjonsnummer - felter som vises på hvert skjema) og dynamiske brukergenererte felter (egendefinerte spørsmål spesifikke for en tilskuddsordning). Disse to typene felter måtte dele valideringslogikk og innsendingsflyter.
I tillegg måtte skjemaene støtte betinget logikk. For eksempel, 'Hvis søkeren velger Ja for spørsmål 3, vis spørsmål 4; ellers skjul det.' Dette krevde en regelmotor som kunne evaluere feltavhengigheter i sanntid.
Google Forms-inspirert byggegrensesnitt Designeren min sa 'det burde være som Google Forms', så jeg prøvde å gjenskape UX-en til Google Forms. Det viktigste å gjenskape var 'klikk-for-å-redigere'-mønsteret: å klikke på et felt gjør det redigerbart; å klikke utenfor auto-lagrer og returnerer til forhåndsvisningsmodus. Denne gode UX-en føles naturlig, men er kompleks å implementere.
Problemet: Hvordan vet komponenten om et klikk er 'innenfor' eller 'utenfor' når 'innenfor'-området endres dynamisk etter hvert som felter legges til? Jeg implementerte en ref-basert løsning med en tilpasset hook. Hvert redigerbart felt registrerer sin DOM-node i en kontekst, og en global event-lytter sjekker om klikkmålet er en etterkommer av en registrert node.
Auto-lagring la til enda et lag med kompleksitet. Jeg brukte en 'debounced' lagringsfunksjon som venter 500ms etter siste tastetrykk før den persisterer til serveren. Dette forhindrer API-spam samtidig som det sikrer at endringer aldri går tapt. Jeg implementerte også optimistiske UI-oppdateringer - skjemaet reflekterer umiddelbart endringer mens lagringen pågår, og viser en subtil 'Lagrer...'-indikator som forsvinner ved suksess.
Validering var vrient fordi både statiske og dynamiske felter trengte validering. Jeg implementerte mitt eget valideringsskjema-system som fletter sammen statiske feltregler (f.eks. 'Navn er påkrevd') med dynamiske regler definert i skjemakonfigurasjonen (f.eks. 'Spørsmål 5 må være et tall mellom 1-100').
Betinget logikk og regelmotor For å støtte betingede felter ('Vis felt B bare hvis felt A er lik X'), bygde jeg en lett regelmotor. Regler lagres som JSON-objekter med en enkel struktur: `{ field: 'question_3', operator: 'equals', value: 'Yes', action: 'show', targets: ['question_4'] }`.
Skjemarendereren abonnerer på feltendringer via React Hook Forms `watch()`-API. Når et overvåket felt endres, evaluerer regelmotoren alle regler som refererer til det feltet, og oppdaterer synlighetstilstanden til avhengige felter. Dette krevde nøye optimalisering - evaluering av hundrevis av regler ved hvert tastetrykk kunne kvele ytelsen.
Jeg optimaliserte ved å bygge en avhengighetsgraf ved skjemainitialisering. Denne grafen mapper hvert felt til reglene som avhenger av det, slik at motoren kun trenger å evaluere relevante regler når et felt endres. For et skjema med 50 felter og 20 regler reduserte dette evalueringstiden fra ~15ms til <1ms per tastetrykk.
Komponentbibliotek og designsystem For å forsikre et forént visuelt uttrykk og redusere kodeduplisering på tvers av de to applikasjonene, etablerte jeg et komponentbibliotek. Hvert gjenbrukbart mønster - knapper, inputs, modaler, nedtrekksmenyer - ble en komponent i en delt npm-pakke. Dette sikret visuell konsistens, forbedret vedlikeholdbarheten og akselererte utviklingshastigheten for resten av teamet.
Begge applikasjonene, men spesielt admin-grensesnittet, inkluderer mange datatabeller for visning av tilskuddssøknader, sporing av godkjenningsarbeidsflyter, generering av rapporter og mye mer. Jeg standardiserte disse ved bruk av React Table (nå TanStack Table), og laget en gjenbrukbar `Table`-komponent med innebygd filtrering, sortering, paginering, radvalg og en kontekstmeny. Tabellen mottok standard React Table-props, noe som gjorde det enkelt å plugge inn et hvilket som helst datasett.
Arkitekturmønstre: Separering av ansvar Jeg etablerte et strengt arkitekturmønster for å forhindre at kodebasen ble spaghetti: 'container'-komponenter på toppnivå håndterer datahenting via React Query, mens 'presentation'-komponenter mottar data via props og fokuserer utelukkende på rendering.
For eksempel henter `ApplicationListPage`-containeren data ved bruk av `useApplications()`, håndterer lasting/feil-tilstander og sender dataene til `ApplicationListTable`, en ren presentasjonskomponent.
React Context API ble brukt mye for tilstand spesifikk for en gitt rute, hvor hver side hadde sin egen context provider. Dette var et forsøk på å unngå 'prop drilling' og holde UI-komponenter strengt presentasjonelle, men jeg ville ikke implementert en slik arkitektur igjen i dag.