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.
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:
... 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:
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:
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