Sånn lagrer du state lokalt i React - uten cookie

- Dessverre ingen innebygget måte å gjøre dette på, skriver Kaptein Krok. Men du fikser det enkelt med hooken usePersistedState.

Kristofer Giltvedt Selbekk, aka Kaptein Krok, viser deg hvordan du lagrer data i React-appen din gjennom en egen hook og localStorage. 📸: Ole Petter Baugerød Stokke / Privat
Kristofer Giltvedt Selbekk, aka Kaptein Krok, viser deg hvordan du lagrer data i React-appen din gjennom en egen hook og localStorage. 📸: Ole Petter Baugerød Stokke / Privat Vis mer

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

Denne gangen besøker vi en hook som husker på ting — selv neste gang du besøker siden!

Greit å huske på

Noen ting er rett og slett greit å huske på.

Hvilke farger du foretrekker, om du har godkjent GDPR-vilkårene, og kanskje til og med hvordan du foretrekker tabellene dine sortert — alt dette er gode kandidater for å huske mellom forrige gang du var innom en side og neste gang.

Dessverre er det ingen innebygget måte å gjøre dette på i React. Heldigvis er det overraskende enkelt å lage!

I dag skal vi lage en hook som lagrer ting i localStorage, og husker på ting mellom besøkene!

Implementasjons-tid!

Målet vårt i denne artikkelen er å lage en custom React hook som lar deg persistere vilkårlig data, og hente det ut igjen. Vi kaller den usePersistedState, og det hadde vært veldig fint om det opplevdes likt som Reacts innebygde useState hook.

localStorage er ganske enkelt å bruke — du lagrer ting med localStorage.setItem("nøkkel", "verdi") og henter ting med localStorage.getItem("nøkkel").

En liten ulempe er at man kun kan lagre strenger, men det kan vi komme rundt ved å bruke JSON.stringify og JSON.parse for å serialisere og deserialisere eventuelle objekter og arrayer.

Det første vi må gjøre er å ha en lokal React-kopi av tilstanden. Den initielle versjonen burde vi hente fra localStorage, og om det ikke finnes, burde vi få lov til å sende inn en initiell tilstand (akkurat som useState ).

Førsteutkastet av hooken vår ser slik ut:

const usePersistedState = (key, initialState) => {
  const [state, setState] = React.useState(
    localStorage.getItem(key) !== undefined 
    ? JSON.parse(localStorage.getItem(key))
    : initialState
  );
  return [state, setState];
};

Her sier vi “hvis det finnes noe lagret i localStorage under key, så hent ut den informasjonen. Hvis ikke, default til initialState.”

Dette fungerer jo nesten — bortsett fra at ingenting blir lagret i localStorage da! Det burde vi fikse opp i.

Hver gang noe endrer seg i state , burde vi lagre det i localStorage — og da er React.useEffect det perfekte verktøyet å gripe etter:

const usePersistedState = (key, initialState) => {
  const [state, setState] = React.useState(
    localStorage.getItem(key) !== undefined 
    ? JSON.parse(localStorage.getItem(key))
    : initialState
  );
  React.useEffect(() => {
    localStorage.setItem(key, JSON.stringify(state));
  }, [state, key]);
  return [state, setState];
};

Hver gang vi endrer state, lagrer vi den serialiserte versjonen av tilstanden i localStorage. I tillegg vil vi gjøre det samme om key endrer seg.

La oss ta en titt på hvordan dette kan brukes:

const ToggleThemeButton = () => {
  const [theme, setTheme] = usePersistedState('theme', 'light');
  return (
    <button 
      type="button" 
      className={theme === 'light' ? 'light-button' : 'dark-button'}
      onClick={() => 
        setTheme(prev => prev === 'light' ? 'dark' :'light')
      }
    >
      Toggle theme
    </button>
  );
}

Det ser ganske likt ut som en god gammeldags setState, sant? Den eneste forskjellen er at man trenger å spesifisere en unik lagringsnøkkel — nøkkelen man lagrer ting under i localStorage.

Men hva med server-side?

Denne hooken fungerer fantastisk i de aller fleste tilfeller — i alle fall frem til man skal gjøre noe med server-side rendering i bildet. Da er plutselig ikke localStorage tilgjengelig, og ting kræsjer verre enn Michael Schumacher i skiløypa (too soon? huff, jeg savner schumi 😢).

Én tilnærming er å rendre den initielle tilstanden på serveren, og den lagrede tilstanden på klienten, slik:

const usePersistedState = (key, initialState) => {
  const [state, setState] = React.useState(
    localStorage.getItem(key) !== undefined 
    ? JSON.parse(localStorage.getItem(key))
    : initialState
  );
  React.useEffect(() => {
    const item = localStorage.getItem(key);
    if (item !== undefined) {
      setState(JSON.parse(item));
    } 
  }, [key]);
  React.useEffect(() => {
    localStorage.setItem(key, JSON.stringify(state));
  }, [state, key]);
  return [state, setState];
};

Dette fungerer greit, men kan føre til et lite “flash” av den ene verdien før den andre verdien blir lagt til.

Et litt bedre alternativ er å bytte ut localStorage med cookies, og lese inn og parse innholdet der server-side. Måten dette må implementeres på er litt avhengig av hvilken SSR-løsning du har, og litt out of scope for denne lille artikkelen, men er definitivt løsbart. Her er en guide til hvordan du kan gjøre det i NextJS.

Kort oppsummert

Trenger du å huske på noe om brukeren din?

Da kan det være smart å ha en hook i arsenalet som lar deg gjøre nettopp det.

Med usePersistedState på laget, slipper du i alle fall å lure på hvordan det gjøres.

Har du en krok-idé?

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