Poznajemy czym jest updater function i do czego może nam się przydać korzystanie z niej. Kontynuacja poprzednich lekcji, do dzieła.

W React mamy wiele powodów do korzystania z funkcji:

  • RFC, komponent funkcyjny – funkcja zwracająca JSX i korzystająca z hooków na swoim najwyższym i niezagnieżdżonym poziomie
  • Funkcja cleanup – używana w useEffect, przeciwieństwo setup, wiemy już jak z niej korzystać i po co
  • Funkcja eventowa – gdy chcemy na onclick na przykład wywołać jakiś handler, ale przekazać mu argumenty
  • Funkcja initializer – przekazywana do useState funkcja tworząca initial state (jeden raz, chyba że nie wiemy co robimy i przekażemy tam zawołanie funkcji)
  • Funkcja reducer – przekazywana do useReducer, jeszcze poznamy
  • Funkcja definiowana od zera przy każdym re-renderze – każda funkcja wewnątrz komponentu funkcyjnego, chyba że użyjemy useCallback (poznamy)
  • Funkcja definiowana 1 raz, totalnie niezmienna – funkcja powyżej poziomu rfc
  • Funkcja custom hook – musi zaczynać się od słowa use, każdy argument do niej przekazany jest traktowany jako zależność równa innym reaktywnym zmiennym będącym zazwyczaj zależnościami, może używać hooków
  • Funkcja asynchroniczna – ani rfc nie może być asynchroniczne, ani callbacki do hooków, ale wewnątrz useEffect (albo wewnątrz rfc albo powyżej rfc) możemy utworzyć funkcję ze słówkiem async i wykorzystać ją z await wewnątrz useEffect
  • Funkcja updater – przyjmuje poprzedni stan do callbacka i choć nie zna tego stanu, wie jak mu update robić

Tą ostatnią się teraz zajmiemy. Są dwa zastosowania do niej:

  • Jeżeli chcemy wywołać setState kilka razy z rzędu w jednym handlerze
  • Jeżeli chcemy wywalić state z tablicy zależności

Ok, najpierw ten pierwszy przypadek. Wyobraźmy sobie, że coś takiego mamy:

export default function Form() {
  const [name, setName] = useState('Taylor');
  const [age, setAge] = useState(42);

I teraz tak – chcemy zrobić inkrementację age 3 razy z rzędu:

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}

I nie działa. Nie dość, że korzystamy z age, zmiennej stanowej (bardzo często jest to obarczone koniecznością dopisania do tablicy zależności np. w useEffect) to jeszcze głupi React nie ogarnia.

No, może nie głupi, dla niego stan się nie zmieni, póki handler się nie skończy, jest cały czas 42.

Teraz updater function:

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

To a to tzw. prevState. Nie dość, że nie jest on zależnością (i handleClick nie wie, jaką ma wartość) to jeszcze podanie updatera powoduje, że bez problemu radzi sobie z wykonaniem inkrementacji 3 razy pod rząd.

Ok, teraz z Githuba o nazwie academind (też znamy twórca) podaję przykład korzystania z updatera do usuwania zbędnych zależności z tablicy:

import { useState, useEffect } from 'react';

export default function ProgressBar({ timer }) {
  const [remainingTime, setRemainingTime] = useState(timer);

  useEffect(() => {
    const interval = setInterval(() => {
      setRemainingTime((prevTime) => prevTime - 10);
    }, 10);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <progress value={remainingTime} max={timer} />;
}

Jak widać remainingTime nie jest zależnością useEffect. Funkcja setState nigdy nią nie jest, ale samo state już tak, natomiast korzystając z konwencji prevState i updatera możemy olać taką zależność i przekazać instrukcje jak ma być wykonany update niezależnie jaka tam jest wartość.

Bo warto wiedzieć – gdyby state tam było zależnością, to doszłoby do pętli nieskończonej:

  • w setState korzystamy z state – 10
  • w związku z tym dopisujemy state do zależności
  • w związku z tym, ilekroć state się zmieni useEffect się odpala od nowa

Ok, więcej o React już niedługo…