React Hooks
Funksjoner som alltid starter med use.
useSomething();
Hooks er funksjoner som er tilgjenglige for deg som utvikler, og trenger ikke nødvendigvis ha noe med hverandre å gjøre, eller være en del av lifecycle til komponenten. useState bryr seg ikke om lifecycle på samme måte som useEffect.
Kan kun kalles i toplevel av en komponent funksjon.
function App() {
useHook(); //✅
const func = () => {
useHook(); //❌
}
return <button onClick={()=> useHook()}></button> {/* ❌ */}
}
Dette gjelder ikke for custom hooks.
useState
Brukes for å holde data/state inne i én komponent.
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<button onClick={()=>setCount(count+1)}>
{count}
</button>
);
}
Her settes initiell state til 0. Funksjonen returnerer et array.
Første element er verdien. Andre element er en funksjon for å sette verdien.
Når setCount kalles, vil count oppdateres i UIet.
useEffect
Brukes istedenfor mer tradisjonelle onMount, onUpdate og onUnmount funksjoner.
Den vil kjøre funksjonen den tar som første argument idet react oppdaterer DOMen.
useEffect(()=>{
console.log('I updated')
})
Den funksjonen vil kjøre på hver update. Det kan bli problematisk i følgende kodesnutt:
const [loaded, setLoaded] = useState(false);
useEffect(()=>{
fetch('foo').then(()=>setLoaded(true));
});
Idet fetchen resolver vil den oppdatere staten, som vil oppdatere DOMen, som vil trigge effekten. Dermed er vi inni en evig loop.
Dette fikses ved at den tar ett 2. argument. En liste over avhengigeter. Dvs. at den kun trigges dersom en av variablene i listen har endret seg. (Dette burde være mulig å dra uti et kompileringssteg, slik som Vues computeted egenskaper.)
En tom liste betyr at den ikke har noen avhengigheter, og vil i praksis bli kjørt 1 gang.
Denne blir ekvivalent med onMount og vil bare kjørt på intial render.
const [loaded, setLoaded] = useState(false);
useEffect(()=>{
fetch('foo').then(()=>setLoaded(true));
},[]);
Denne vil kjøre hver gang count blir oppdatert. I tillegg til den initielle renderen.
const [count, setCount] = useState(0);
const [loaded, setLoaded] = useState(false);
useEffect(()=>{
fetch(`foo/${count}`).then(()=>setLoaded(true));
},[count]);
Om man returnerer en funksjon i useEffect, så vil React kalle denne når en komponent blir destroyed. Dette blir altså som onUnmount. Her kan man skrive cleanupkode.
const [count, setCount] = useState(0);
const [loaded, setLoaded] = useState(false);
useEffect(()=>{
function print(){
console.log("Mouse Click!");
}
document.addEventListener("mousedown", print)
return () => document.removeEventListener("mousedown", print)
},[]);
useContext
Lar deg bruke context APIet til react. Dette er et API som lar deg dele data på tvers av flere komponenter.
const colors = {
green: '🟩',
blue: '🟦'
};
const ColorContext = createContext(colors);
function App(){
return (
<ColorContext.Provider value={colors.green}>
<ColorSquare />
</ColorContext.Provider>
);
}
function ColorSquare() {
const color = useContext(ColorContext); //👈 Henter verdi fra nærmeste parent provider
return <p>{color}</p>
}
useRef
Lar deg lage et muterbart objekt som beholder samme referanse for hver render. Litt som useState, men vil ikke endre UI om man oppdaterer verdien. (Man kan ikke bare bruke en vanlig Javscript variabel, ettersom komponent-funksjonen kalles på nytt for hver re-render).
function App() {
cont count = useRef(0);
return (
<button onClick={()=>count.current++}>
{count.current}{/*Denne vil ikke oppdateres i UI når man trykker på knappen. Current er verdien som er lagret.*/}
</button>
);
}
Kan brukes om man må holde på state som ikke skal reflekteres i UI.
Som regel brukes den for å hente HTML elementer fra JSX, og gjøre dem tilgjengelig programmatisk.
function App() {
const btn = useRef(null);
const clickBtn = () => btn.current.click();
return (
<button ref={btn}></button>
);
}
Her kan man bruke clickBtn funksjonen for å trigge et klikk-event programmatisk.
useReducer
Ligner på setState, men bruker Redux mønsteret for å gjøre det. Dvs. at en Action blir trigget, som en reducer plukker opp. Den "regner ut" staten og oppdaterer en store som deretter oppdaterer UI.
graph LR; UI-->Action; Action-->Reducer; Reducer-->Store; Store-->UI;
function reducer(state, action){
switch(action.type){
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, 0);//Parametre: Reduce funksjonen, initiell state
return (
<>
Count: {state}
<button onClick={()=> dispatch({type: 'decrement'})}>-</button>
</>
);
}
Mer komplekst enn å bare bruke state, men kan være behjelpelig dersom kompleksisteten vokser. All mutasjon av state skjer ett sted, istedenfor at flere forskjellige funksjoner setter sin versjon av state.
useMemo
Memoization. Cacher resultat fra et funksjonskall. Brukes dersom et funksjonskall er veldig kostbart, og man ikke ønsker å regne ut resultatet på hver rerender. Bør brukes dersom man har performance issues.
Når skal man bruke memoization i React hooks
function App(){
const [count, setCount] = useState(0);
const expensiveCount = useMemo(()=>{
return count ** 2;
}, [count]);
}
Denne vil kun kjøre expensiveCount utregningen dersom count har endret seg.
useCallback
Samme som useMemo, men for funksjoner istedenfor verdier.
functino App() {
const [count, setCount] = useState(0);
const alertPeople = useCallback(() => {
alert('The count is: ' + count);
}, [count])
return <SomeChild handler={alertPeople} />
}
Siden alertPeople funksjonen nå er memoizert, så vil hver rerender brukes samme funksjonsreferanse. (Funksjoner er "first class citizens" i JS. De er et objekt som alle andre objekter.) Da referanseverdien forblir lik vil ikke rerender av komponenten trigge rerender av alle child-komponentene, da funksjonsreferansen ikke har endret seg. (En child-komponent vil rerendres dersom noen av propsene endrer seg. F.eks. en funksjons referanse.)
Det vil si at hver gang count oppdateres i komponenten over, så vil hele funksjonen App kjøres på nytt. Da vil SomeChild også rendres på nytt fordi alertPeople er nå en ny funksjon!
Samme regel gjelder her: Ikke forhånds optimaliser. Det gjør koden unødvendig kompleks. AHA - Avoid Hasty Abstraction
useImperativeHandler
Sjelden det er bruk for den. Brukes for å omdefinere hvordan en eksponert referanse til et HTML element i f.eks. et bibliotek oppfører seg.
function MyButton() {
const btn = useRef(null);
useImperativeHander(ref, ()=>{
click: ()=>{
console.log("The button was clicked");
btn.current.click();
}
});
return (
<button ref={btn}></button>
);
}
MyButton = forwardRef(MyButton);//Gjør ref tilgjengelig dersom noen bruker komponententen
useLayoutEffect
Ligner på useEffect. Vil bli kalt etter at React har rendret komponenten men før det er synlig på skjermen. React vil vente på at denne er ferdig før den oppdaterer UIet.
Denne brukes sjelden. Vil være nyttig dersom man er nødt til å vite noe om DOMen før man viser det til brukeren. F.eks. til å sette en scroll posisjon.
function App(){
const myBtn = useRef(null);
useLayoutEffect(()=>{
const rect = myBtn.current.getCoundingClientRect();
console.log(rect.height);
});
}
Custom Hooks
Man kan lage egne hooks som kan gjenbrukes på tvers av komponenter.
function useUserInfo(){
const [userInfo, setUserInfo] = useState(null);
useEffect(()=>{
let response = await(await fetch(userInfoApi)).json();
setUserInfo(response);
}, []);
return userInfo;
}
function App(){
const userInfo = useUserInfo();
return <b>{userInfo.name}</b>
}
useDebugValue
Brukes for å legge til custom labels i React Dev Tools når man lager custom hooks.
function useUserInfo(){
const [userInfo, setUserInfo] = useState(null);
useEffect(()=>{
let response = await(await fetch(userInfoApi)).json();
setUserInfo(response);
}, []);
useDebugValue(userInfo ?? 'loading...'); // 👈
return userInfo;
}
function App(){
const userInfo = useUserInfo();
return <b>{userInfo.name}</b>
}
Denne linjen kode vil føre til at hooken heter "UserInfo" i react dev tools vinduet når vi inspiserer App komponenten.