24-Dec

React, Development

Hva er greia med useRef og forwardRef?

Du har kanskje støtt på useRef og forwardRef før, men ikke helt fått koll på hva det dreier seg om. I denne korte artikkelen tar jeg vekk mystikken, og gir noen enkle, nyttige eksempler på hva de kan brukes til.

3 min read

·

By Marcus Haaland

·

December 24, 2023

La oss starte med noe som du sikkert er kjent med. Ta en titt på søkefeltet over. I Vanilla JavaScript, hvordan ville du ha satt fokus på et element idet du entret en nettside?

En vanlig tilnærming ville vært å bruke document.querySelector:

<!-- input.html -->
<input id="input">

<script>
window.onload = function() {
  // 👇 Putter referansen til elementet inn i en variabel
  const inputElement = document.querySelector('#input');

  // 👇 Bruker referansen til å sette fokus
  if (inputElement) {
    inputElement.focus();
  }
};
</script>

Men om du i React bruker document direkte, omgår du den virtuelle DOM-en, som kan føre til synkroniseringsproblemer og gjøre koden mindre forutsigbar.

Så fins det noe som tilsvarer document.querySelector i React?

Hvordan useRef fungerer

useRef i React er en hook som kan gi samme funksjonalitet som querySelector, men på en måte som er kompatibel med Reacts måte å håndtere DOM på.

Slik ser samme oppførsel ut implementert med useRef:

import React, { useRef, useEffect } from "react";

const InputFocusComponent = () => {
  // 👇 ref starter som null, men får verdien som InputElement
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);
  
  // 👇 ref kobles til elementet
  return <input ref={inputRef} />;
}

Som du ser fra typen, refererer useRef her til et inputelement. Men vi starter verdien med en null-verdi, fordi den får ikke en verdi før elementet har blitt tegnet i dokumentet.

Referansen til elementet gir tilgang til alle dens verdier og funksjoner:

Et element har en lang liste av verdier og funksjoner tilgjengelig
Et element har en lang liste av verdier og funksjoner tilgjengelig

... og det inkluderer å sette fokus:

inputRef.current.focus();

Hvorfor trenger du forwardRef?

useRef fungerer fint på HTML-elementer, men når du skal prøve å referere komponenter, dukker det opp problemer:

const InputFocusComponent = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    // 👇 ref.current vil alltid være null
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);

  // 👇 ref på en komponent fungerer ikke av seg selv
  return <CustomInput ref={inputRef} />;
}

Problemet er at ref er et reservert ord, og vil derfor ikke være tilgjengelig som en del av props.

Dette får vi varsel om i loggen:

Å sette ref på komponenter uten forwardRef feiler
Å sette ref på komponenter uten forwardRef feiler

Loggen gir oss ikke kun et rødt hinder, men også en løsning. Når vi bruker ref på et element som er en komponent, må vi omslutte komponenten med høyere-ordens-komponenten forwardRef.

Merk også at forwardRef<> aksepterer to typer argumenter: typen av elementet og typingen for egenskapene (props).

import React, { useRef, useEffect, forwardRef } from "react";

// 👇 Innkapsler CustomInput med forwardRef
const CustomInput = forwardRef<
  HTMLInputElement, // 👈 Vi typer elementet
  React.InputHTMLAttributes<HTMLInputElement> // 👈 Vi typer propsene
>((props, ref) => {
  return <input ref={ref} {...props} />;
});

forwardRef er veldig nyttig for å kunne gi kontroll av komponentene dine, enten det er for din egen del eller et bibliotek du tilbyr.

Hva annet enn fokus kan du bruke useRef til?

Det er en spesiell egenskap ved useRef, som fører til at det ikke kun er nyttig for å referere til HTML-elementer. Egenskapen er at useRef starter livet sitt som et objekt, {current: null}. Det fører til at verdien vi bryr oss om, current, kan vi endre uten å rendre hele komponenten på nytt.

Det gjør det mulig å bruke useRef til å lagre verdier på tvers av re-renders. Disse verdiene må ikke være HTML-elementer, som fører til at du kan bruke useRef til en rekke anvendelser. Du kan for eksempel referere til tidsintervaller for å implementere debouncing eller telle opp endringer på tvers av renders.

Her er et enkelt eksempel på sistnevnte. Ved hver inntasting, vil en re-render skje. Og re-renderen vil inkrementere verdien for ref-en:

function CountEveryRender() {
  const [inputValue, setInputValue] = useState<string>("");
  const count = useRef<number>(0);

  // 👇 Øker på hver render
  count.current = count.current + 1;

  return (
    <>
      <input
        onChange={(e) => setInputValue(e.target.value)}
        value={inputValue}
      />
      <p>Render Count: {count.current}</p>
    </>
  );
}

Slik ser det ut:

Skriving i inputfelt øker en teller
useRef kan brukes til å holde rede på en verdi på tvers av renders

Avsluttende ord

Jeg håper denne posten hjalp deg å forstå litt mer av hva useRef og forwardRef prøver å løse, og hvordan de fungerer i hverdagslige utfordringer.

Om du vil lese mer om riktig typing av ref, anbefaler jeg Matt Pococks enkle tilnærming med ElementRef: https://www.totaltypescript.com/strongly-type-useref-with-elementref