20-Dec

React

Ta kontroll på skjemaelementet!

Det er lett å blande mellom kontrollerte og ukontrollerte komponenter i React, men hva er egentlig forskjellen og hvilken burde man bruke?

4 min read

·

By Einar Afiouni

·

December 20, 2023

Har du noen gang opplevd å få denne meldingen?

Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen.

Da er du ikke alene. React har nemlig to forskjellige måter å håndtere tilstand i skjemaelementer og det er lett å plutselig ta i bruk begge uten å være klar over det. Så hva er forskjellen?

Controlled

En kontrollert komponent er når du selv håndterer tilstanden til komponenten i React ved bruk av for eksempel useState. På denne måten har man selv kontroll på hvor og hvordan man velger å lagre tilstanden og hva man vil gjøre med den etterpå. Det er denne måten React selv anbefaller at man håndterer tilstand på skjemaelemeneter.

const Controlled = () => {
  const [state, setState] = useState<string>("");
  return <input value={state} onChange={(e) => setState(e.target.value)} />;
};

Så når man skal lagre verdien senere er det bare å sende med state videre.

const submit = () => onSave(state);

Ønsker man å ha en en startverdi på en kontrollert komponent er det altså bare å sette denne som verdien av value.

const [state, setState] = useState<string>("startverdi");

Fordelen med kontrollerte komponenter er at man har full kontroll på dataflyt og det er lett å bearbeide data, reagere på endringer og validere underveis. Ulempen kan være at det blir mye mer kode og tilstandshåndtering som skal til når man har mange skjemaelementer.

Uncontrolled

Når en komponent ikke er kontrollert betyr det at vi etterlater tilstandshåndteringern til den faktiske DOM-en, utenfor React og dens Virtual DOM.

const Uncontrolled = () => {
  const ref = useRef<HTMLInputElement>(null);
  return <input ref={ref} />
}

For å hente verdien av skjemaelementet da må vi koble oss på selve DOM-elementet under React sin kopi. Dette gjør man med å lage en referanse som man sender til komponentet med ref. Når man skal lagre verdien senere er det bare å hente nåværende verdi gjennom referansen vi har laget

const submit = () => onSave(ref.current?.value);

Man skal altså ikke sende inn value inn til en ukontrollert komponent for da blir den kontrollert. Hvis man ønsker å ha en startverdi kan man gjøre dette ved bruk av defaultValue.

<input ref={ref} defaultValue="startverdi" />

Siden referanser peker på verdier utenfor Virtual DOM-en så reagerer ikke React på endringer i referanseverdien og komponenter vil ikke re-rendre. Det betyr for eksempel at en useEffect vil ikke utløses av en endring i referanseverdien selv om du har den som en avhengighet.

Fordelen med ukontrollerte komponenter er at man slipper å håndtere mange tilstander hvis man har mange skjemaelementer. Ulempen derimot er at man mister muligheten til å bearbeide, validere og reagere på verdiendringer som på mange måter er en av React sine styrker.

Alternativt kan man også forbipassere React helt ved hjelp av en <form>. Da trenger man heller ikke sende referanser inn til skjemaelementene og verdiene sendes videre som en helt vanlig HTML form-submit.

A component is changing

Så hvordan får man meldingen jeg skrev innledningsvis? Den får man hvis man endrer en komponent fra ukontrollert til kontrollert. Dette kan skje av mange grunner, men den vanligste er kanskje at man ikke har noen startverdi på komponenten.

const Confused = () => {
  const [state, setState] = useState<string>();
  return <input value={state} onChange={(e) => setState(e.target.value)} />;
};

Da vil komponenten være ukontrollert ved første render siden verdien er undefined, men så fort man har endret på verdien og value får en verdi så endres komponenten fra en ukontrollert til en kontrollert komponent.

En annen vanlig feil som er lett å gjøre er å sende med defaultValue selv om komponenten er kontrollert. Da vil man få denne meldingen

Warning: Controlled contains an input of type undefined with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both).

Det er altså lett å gjøre feil, og det er heller ingen stor fare. Det er derfor dette er markert som en advarsel og ikke som en feil. Det gjelder bare å ha tunga rett i munn og være konsekvent på hvilken form av tilstandshåndtering man vil ha.