iI gamle dager, for sånn… 3 år siden, skrev vi React-koden vår ganske annerledes enn vi gjør nå. På den tiden brukte vi såkalte klassekomponenter, med “livssyklusmetoder” for å reagere på endringer i props og state.
Koden din kunne for eksempel se slik ut:
class Eksempel extends React.Component {
componentDidUpdate(prevProps, nextProps) {
if (prevProps.id !== nextProps.id) {
analytics.log(
`ID endret seg fra ${prevProps.id} til ${nextProps.id}`
);
}
}
render() {
return <h1>Hei hei</h1>;
}
}
Nå bruker man den litt mer hendige useEffect hooken til å oppnå det samme. Fordelene er mange, men en av ulempene er at man ikke lenger mottar forrige versjon av propsene — og da blir plutselig dette vanskelig, eller?
const Eksempel = (props) => {
useEffect(() => {
analytics.log(
`ID endret seg fra ${???} til ${nextProps.id}`
);
}, [props.id]);
return <h1>Hei hei</h1>;
}
Hvor skal vi få prevProps fra? Blir man tvunget til å bruke klassekomponenter igjen?!? 👀
useState vs useRef
Før vi titter på hvordan vi løser dette med moderne React-APIer, tenkte jeg det var greit å gi en liten innføring i forskjellen mellom to innebygde React-hooks — nemlig useState og den noe mer eksotiske useRef.
Om du har skrevet React i det siste, har du sikkert vært borti useState . Du sender inn en initiell verdi, og returnerer et tuple med verdien, og en funksjon for å oppdatere verdien.
const [id, setId] = React.useState('min-id');
console.log(id); // 'min-id'
Hvorfor trenger vi det egentlig? Jo, det er for at React skal kunne legge merke til at verdien endret seg, og så “rendre” — eller kalle — komponenten din igjen for å oppdatere seg igjen. Om state-settere kunne prate, ville nok setId sagt noe slikt:
«“Hei React, nå har `id` fått ny verdi! Kall komponenten min på nytt, så du ser hvordan ting burde endre seg!”»
Det finnes en annen måte å holde på verdier på tvers av komponent-kall (renders) også, og det er med useRef . Som i useState sender du inn en initiell verdi, men her får du istedenfor tilbake et objekt med nøkkelen current og den initielle verdien din som verdi.
const idRef = useRef('min-id')
console.log(idRef.current); // 'min-id'
Bortsett fra at strukturen er litt annerledes, er det en viktig forskjell mellom de to — når du endrer verdien til idRef , sier du ikke ifra til React! Derfor rendrer ikke React komponenten din på nytt, og får egentlig ikke med seg endringen.
Dette kan være veldig nyttig av og til — blant annet når du vil huske på forrige verdi av noe.
usePreviousValue
La oss lage en custom krok for å huske på forrige verdi. Den kommer til å se slik ut:
const usePreviousValue = (value) => {
const previousRef = useRef();
useEffect(() => {
previousRef.current = value;
}, [value]);
return previousValue.current
}
Det ser kanskje litt merkelig ut? Ikke fortvil —jeg skal forklare.
Vi starter med å lage oss en ny ref med useRef — dette er den som skal holde på den “forrige” verdien vår. Vi vil ikke at React skal re-rendre bare fordi den forrige verdien endret seg — den vil jo alltid endre seg i takt med verdien den følger.
Neste steg er å oppdatere den “forrige verdien” hver gang den “neste verdien” oppdaterer seg. Det bruker vi en useEffect til å oppnå. Vi sender inn value i avhengighetslista, slik at sideeffekten kjører hver gang value endres, og “forrige verdi” oppdateres til å være den “nye forrige verdien”.
Helt til slutt returnerer vi den forrige verdien, slik at vi kan bruke den i koden vår!
const Eksempel = (props) => {
const previousId = usePreviousValue(props.id);
useEffect(() => {
analytics.log(
`ID endret seg fra ${previousId} til ${nextProps.id}`
);
}, [props.id]);
return <h1>Hei hei</h1>;
}
Eller om du vil huske på alle props:
const Eksempel = (props) => {
const prevProps = usePreviousValue(props);
useEffect(() => {
analytics.log(
`ID endret seg fra ${prevProps.id} til ${nextProps.id}`
);
}, [props.id]);
return <h1>Hei hei</h1>;
}
Nyttig triks å kunne
Dette kan være en nyttig teknikk å ha i ermet. Som et eksempel kan vi jo forbedre usePersistedState hooken vi laget i denne artikkelen? La oss fjerne ting fra local storage om keyen endrer seg:
const usePersistedState = (key, initialState) => {
const [state, setState] = React.useState(
localStorage.getItem(key) !== undefined
? JSON.parse(localStorage.getItem(key))
: initialState
);
const previousKey = usePreviousValue(key);
React.useEffect(() => {
const item = localStorage.getItem(key);
if (item !== undefined) {
setState(JSON.parse(item));
}
}, [key]);
React.useEffect(() => {
if (previousKey !== key) {
localStorage.deleteItem(key);
}
localStorage.setItem(key, JSON.stringify(state));
}, [state, key]);
return [state, setState];
};
Og vipps, så har vi ryddet opp etter oss også.
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!