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…