Uczymy się jak utrzymywać poprzedni stan oraz jak napisać do tego hook, który robi to za nas plus ciekawostka – do reaktywnych zmiennych również możemy robić ref.

Ok, rzućmy okiem na app.js:

import { useState } from 'react';
import CountLabel from './CountLabel.js';

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <button onClick={() => setCount(count - 1)}>
        Decrement
      </button>
      <CountLabel count={count} />
    </>
  );
}

Mamy tu stan, guziki go zmieniające, mamy komponent CountLabel ze stanem przekazanym jako prop. Przypomnijmy:

  • Każdy re-render rodzica (App) będzie się wiązał z re-renderem dziecka (CountLabel) z nowym prop
  • Jest jednokierunkowy przepływ danych, więc gdyby tak dziecko zmieniło sobie ten prop, to to nie zmieni stanu rodzica, i jest bez sensu
  • Komponenty dzieci mogą zmieniać stan rodzica, o ile im na to pozwolimy za pomocą:
    • Przekazania przez rodzica jako prop funkcji, która modyfikuje jego stan, a dziecko ją może zawołać
    • Przekazania przez rodzica za pomocą slotu jakiegoś markupu wołającego setState rodzica (do props.children)

Ok, ale dzisiaj my nie o tym. CountLabel:

import { useState } from 'react';

export default function CountLabel({ count }) {
  const [prevCount, setPrevCount] = useState(count);
  const [trend, setTrend] = useState(null);
  if (prevCount !== count) {
    setPrevCount(count);
    setTrend(count > prevCount ? 'increasing' : 'decreasing');
  }
  return (
    <>
      <h1>{count}</h1>
      {trend && <p>The count is {trend}</p>}
    </>
  );
}

Czyli tak:

  • Rodzic ma stan count, dziecku przekazany jako prop
  • Ten prop jest wyświetlany w h1
  • Dziecko ma dwa stany prevCount i trend
  • prevCount ma initial value ustawione na prop count
  • trend ma initial value ustawione na null
  • Jeżeli prevCount, który jak każdy stan jest utrzymywany między re-renderami, jest inny niż count (przy każdym re-renderze rodzica przekazywany jako prop dziecku) to znaczy, że:
    • trzeba ustawić nowy prevCount na obecny count, bo już się zmieniły
    • trzeba ustawić trend (rosnący malejący)
  • Dalej mamy conditional rendering – jeżeli jest trend, to go pokazujemy

WebDevSimplified na swoim githubie ma hook usePrevious, bardzo ciekawy:

import { useRef } from "react"

export default function usePrevious(value) {
  const currentRef = useRef(value)
  const previousRef = useRef()

  if (currentRef.current !== value) {
    previousRef.current = currentRef.current
    currentRef.current = value
  }

  return previousRef.current
}

No i co tu się dzieje:

  • hook dostaje value, którym jest reaktywna zmienna (state)
  • do tego value tworzymy ref
  • tworzymy też previousRef do niczego
  • Jeżeli current się zmienił to previous aktualizujemy i current też
  • Zwracamy previous

Użycie w kodzie:

import { useState } from "react"
import usePrevious from "./usePrevious"

export default function PreviousComponent() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState("Kyle")
  const previousCount = usePrevious(count)

  return (
    <div>
      <div>
        {count} - {previousCount}
      </div>
      <div>{name}</div>
      <button onClick={() => setCount(currentCount => currentCount + 1)}>
        Increment
      </button>
      <button onClick={() => setName("John")}>Change Name</button>
    </div>
  )
}

Tutaj wystarczy tylko zmieniać reaktywną zmienną count, aby previousCount nam się sam aktualizował.

Przypomnijmy:

  • ref można utworzyć do elementu DOM komponentu, poprzez utworzenie ref i powiązanie z atrybutem ref
  • ref można utworzyć także do zmiennej reaktywnej
  • refy podobnie jak stan potrafią przetrwać re-render
  • refy w odróżnieniu od stanu gdy się zmieniają nie wywołują re-rendera
  • w JS średniki są opcjonalne dzięki mechanizmowi ASI (automatic semicolor insertion) a sam autor tworzy świetny kod, warty uwagi, ale my nigdy tak sobie nie pozwalajmy na olewanie średników, bo:
    • nie jesteśmy autorem i gdy przez brak średnika ASI zadziała niezgodnie z oczekiwaniem tego nie ogarniemy
    • bo to niedbale

Więcej Reacta niedługo…