Finn skjerm­størrelsen med denne React-hooken

Kaptein Krok viser deg to metoder for å få oppdaterte mål til komponentene dine.

Denne lille artikkelserien introduserer deg til en rekke små React hooks som er praktiske å ha i kodebasen din.

Denne gangen skal vi se på hvordan man kan finne skjermstørrelsen appen er åpen i.

(PS: kode24 har fått seg nye kodebokser. Last sida på nytt et par ganger om du ikke får pene, fargelagte kodebokser!)

Inner-størrelse og resize-events 📏

Vi kan hente ut størrelsen på window-objektet for å få tak i høyde og bredde på skjermen som brukes gjennom innerWidth og innerHeight propertyene.

Da ville “hooken” vår sett noe sånn her ut:

const useWindowSize = () => ({
  width: window.innerWidth,
  height: window.innerHeight
})

Ikke særlig spennende (eller stateful). Men kanskje enda viktigere, den ville kun gitt deg størrelsene på skjermen i det komponenten din ble rendret for første gang.

For å få med oss når brukere endrer vindusstørrelsen må vi gjøre litt mer.

Én første tilnærming er å lage en useEffect som holder verdiene våre oppdatert, med window-propsa som dependencies. Hooken vil da se slik ut:

import { useEffect, useState } from 'react';
const useWindowSize = () => {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  useEffect(() => {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight
    })
  }, [window.innerHeight, window.innerWidth]);
  return windowSize
}

🚨 ERRR! Vi får en advarsel:

"React Hook useEffect has unnecessary dependencies: 'window.innerHeight' and 'window.innerWidth'. Either exclude them or remove the dependency array. Outer scope values like 'window.innerHeight' aren't valid dependencies because mutating them doesn't re-render the component"

Denne forteller oss at det å bruke propsene til window ikke vil fungere i et dependency array, og vi må finne en annen kløktig løsning for å holde verdiene oppdatert. 🤔

Her kommer resize-eventet oss til unnsetning. Istedenfor å sette opp dependencies mot properties fra window, kan vi når hooken rendres første gang, sette opp en event-lytter som forteller oss når skjermen har endret størrelse.

Da får man den ferdige koden som ser sånn her ut:

import { useEffect, useState } from 'react';
const useWindowSize = () => {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });
  
  useEffect(() => {
    const resizeEvent = () => setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight
    });
    window.addEventListener('resize', resizeEvent);
    return () => window.removeEventListener('resize', resizeEvent);
  }, []);
  return windowSize;
};

P.S: Det er røddig å fjerne en EventListener når man er ferdig med den. Det gjør man i retur-verdien av useEffect.

Når du skal bruke hooken blir det kanskje litt som det her:

const phoneMaxWidth = 768;
const Navbar = () => {
  const {width} = useWindowSize();
  return (
    <nav className="navbar">
      {width > phoneMaxWidth && <Menu/>}
      {width < phoneMaxWidth && <PhoneMenu/>}
    </nav>
  );
};

Media Query-måten

Kanskje har man lyst til å finne ut litt mer om klienten?

En annen fremgangsmåte er å bruke window sin matchMedia metode, som gir muligheten for å bruke media queries fra CSS-verdenen.

Teknikken er relativt lik som den forrige, med en useEffect som setter opp en EventListener, men denne gangen på et MediaQueryList-objekt vi får fra matchMedia metoden. Vi blir også nødt å få inn en media query, som useEffect-en vil ha i dependency arrayet.

Hooken blir da seende slik ut:

export function useMediaQuery(query: string) {
  const [matches, setMatches] = useState(false);
  
  useEffect(() => {
    const media = window.matchMedia(query);
    const changeEvent = () => {
      setMatches(media.matches);
    };
    media.addEventListener("change", changeEvent);
    return () => media.removeEventListener("change", (changeEvent));
  }, [query]);
  return matches;
}

Og i samme bruks-eksempel som over vil se slik ut:

const Navbar = () => {
  const isPhone = useMediaQuery('(max-device-width: 768px)');
  return (
    <nav className="navbar">
      {!isPhone && <Menu/>}
      {isPhone && <PhoneMenu/>}
    </nav>
  );
};

Og littegrann ekstra 💅

Denne hooken gir også muligheter for å gjøre andre typer media queries en bare vindusstørrelse.

For eksempel kan du bruke prefers-color-scheme: dark for å finne ut om brukeren foretrekker dark-mode, eller velge og vrake i en haug andre kule media queries!

Har du en krok-idé?

Vi har en rekke enkle hooks som er greie å ha i verktøykassa. Om du har en du er spesielt fornøyd med, så send oss en mail — så kan det godt hende den dukker opp i denne serien!