Przypominamy sobie podstawowe hooki Reacta – useState, useEffect, useRef oraz useReducer. Uczymy się korzystać z API zewnętrznych oraz instalować i wykorzystywać zewnętrzne biblioteki Reacta. Przypominamy sobie własne komponenty oraz ich propsy.
Hook useState – prosty counter
Ugruntujemy sobie wiedzę na temat useState tworząc kolejną aplikację typu licznik. Robimy to po to, aby później zobaczyć to samo z hookiem useReducer i zauważyć różnice i podobieństwa.
Na początek tworzymy aplikację komendą:
npx create-react-app counter-app
Następnie przechodzimy tam poprzez cd (change directory):
cd counter-app
Teraz import useState w App.js:
import React, { useState } from 'react';
W funkcji App, przed returnem używamy hooka:
const [count, setCount] = useState(0);
Następnie tworzymy funkcje increment i decrement:
function increment() {
setCount(count+1);
}
function decrement(){
setCount(count-1);
}
W returnie upraszczamy markup i wyświetlamy count poprzez interpolację:
return (
<div className="App">
<h1>Count: {count}</h1>
</div>
);
Teraz nasze przyciski:
return (
<div className="App">
<h1>Count: {count}</h1>
<button onClick={() => increment()}>Increment</button>
<button onClick={() => decrement()}>Decrement</button>
</div>
);
Możemy odpalić przy pomocy komendy:
npm start
Jak widać nic specjalnego – zwykły licznik. Już to robiliśmy. Warto jednak wspomnieć, że stan początkowy może być wyliczany przy pomocy jakiejś funkcji. Na StackOverflow znalazłem taką funkcję losującą liczbę z zakresu (tak, JS nie ma tego wbudowanego):
function randomIntFromInterval(min, max) { // min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}
Chciałbym to sobie dodać do naszej funkcji App i za każdym razem losować stan początkowy licznika. Dodanie funkcji trudne nie jest, wystarczy przekopiować:
function App() {
function randomIntFromInterval(min, max) { // min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}
Użycie tej funkcji w useState też trudne nie jest, zgodnie z Reactową konwencją zwracamy funkcję strzałkową, która zwraca wynik jakiejś operacji:
const [count, setCount] = useState(() => {
return randomIntFromInterval(1,6);
});
Teraz stan początkowy będzie zawsze losowany. Możemy to sprawdzić odświeżając kilka razy.
Hook useEffect – różne konfiguracje
Doimportujmy sobie useEffect na dobry początek:
import React, { useState, useEffect } from 'react';
Przypomnę – useEffect z pustą tablicą zależności wykonuje się tylko raz, po pierwszym dodaniu komponentu do strony:
useEffect(() => {
console.log("component rendered");
}, []);
To wyświetli nam informację 1 razy, gdy komponent zostanie dodany (2 razy w trybie deweloperskim ze względu na React.StrictMode…)
Przekazanie zależności będzie wywoływało efekt za każdym razem, gdy zależność się zmieni:
useEffect(() => {
console.log(count);
}, [count]);
Świetnie się to nadaje do logowania zmiany stanu bez żadnych komplikacji (wrzucenie takiego loga do funkcji handleClick, zaraz po setState wcale nie jest takim dobrym pomysłem, nie musi i najprawdopodobniej nie będzie poprawnie logować) oraz do różnego rodzaju efektów, które mają się wykonać po zmianie stanu właśnie.
useState i useEffect z pustą tablicą zależności możemy użyć, aby zaciągnąć coś z API po dodaniu komponentu a następnie wyświetlić to w naszej aplikacji:
function App() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.chucknorris.io/jokes/random')
.then(response => response.json())
.then(json => setData(json))
.catch(error => console.error(error));
}, []);
return (
<div>
{data ? <pre>{JSON.stringify(data, null, 2)}</pre> : <div className="loader"></div>}
</div>
);
}
export default App;
Hook useEffect nadaje się też do różnego rodzaju interwałów, pamiętajmy tylko o tym, aby zwrócić funkcję, która czyści interwał:
function increment() {
setCount(count+1);
}
function decrement(){
setCount(count-1);
}
useEffect(() => {
let interval = setInterval(() => increment(), 1000);
return () => clearInterval(interval);
}, [increment]);
Hook useReducer – nowy hook
Rzućmy okiem raz jeszcze na najbardziej uproszczoną wersję naszego licznika:
import React, { useState} from 'react';
function App() {
const [count, setCount] = useState(0);
function increment() {
setCount(count+1);
}
function decrement(){
setCount(count-1);
}
return (
<div className="App">
<h1>Count: {count}</h1>
<button onClick={() => increment()}>Increment</button>
<button onClick={() => decrement()}>Decrement</button>
</div>
);
}
Myślę, że najłatwiej będzie pokazać, jak działa useReducer na przykładzie. Po pierwsze, odpowiedni import:
import React, { useReducer} from 'react';
Teraz funkcja reducer (może być top-level, nad App):
function reducer(state, action) {
if(action.type === 'increment') {
return {
count: state.count + 1
};
}
else if(action.type === 'decrement') {
return {
count: state.count -1
};
}
throw Error('Unknown action.');
}
Już chyba zaczynamy widzieć, do czego to zmierza. Teraz używamy useReducer:
function App() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
Ten hook przyjmuje funkcję reducer oraz stan (jako obiekt), zwraca zaś stan i dispatch. Teraz możemy wyświetlić nasz stan oraz użyć dispatch z odpowiednią akcją w naszych przyciskach:
import React, { useReducer} from 'react';
function reducer(state, action) {
if(action.type === 'increment') {
return {
count: state.count + 1
};
}
else if(action.type === 'decrement') {
return {
count: state.count -1
};
}
throw Error('Unknown action.');
}
function App() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div className="App">
<h1>Count: {state.count}</h1>
<button onClick={() => dispatch({type: 'increment'})}>Increment</button>
<button onClick={() => dispatch({type: 'decrement'})}>Decrement</button>
</div>
);
}
Hook useRef – referencja do obiektu
Hook useRef tworzy nam taką referencję/handle do jakiegoś elementu JSX. Pozwolę sobie wkleić przykład z pierwszej części tutoriala:
import React, { useRef } from 'react';
function FakeForm() {
const inputElement = useRef();
return (
<div>
<input type="text" ref={inputElement}></input>
<button>Log in console</button>
</div>
);
}
export default FakeForm;
Komponent FakeForm, referencja/handle do elementu <input>. Tak tworzymy „uchwyt” do naszego <input>. Teraz możemy z niego skorzystać i po wciśnięciu przycisku wylogować w konsoli wartość naszego <input>:
import React, { useRef } from 'react';
function FakeForm() {
const inputElement = useRef();
function onClickHandler() {
console.log(inputElement.current.value);
}
return (
<div>
<input type="text" ref={inputElement}></input>
<button onClick={() => onClickHandler()}>Log in console</button>
</div>
);
}
export default FakeForm;
Możemy też po wylogowaniu wyczyścić nasz input – ref to pełnoprawny uchwyt i może tak odczytywać jak i nadpisywać:
import React, { useRef } from 'react';
function FakeForm() {
const inputElement = useRef();
function onClickHandler() {
console.log(inputElement.current.value);
inputElement.current.value = '';
}
return (
<div>
<input type="text" ref={inputElement}></input>
<button onClick={() => onClickHandler()}>Log in console</button>
</div>
);
}
export default FakeForm;
Własny komponent i props – LoremItem
Tworzymy własny komponent (w App.js, nad App), który będzie wyświetlać nam „Lorem Ipsum”. Krok pierwszy:
function LoremItem(props) {
const loremText = "Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure veniam sed est rem quam ab a quis? Ipsum in, dolore ad eligendi ex maxime consequuntur quasi laudantium omnis? Dignissimos, ab!"
}
Przyjmuje propsy, posiada loremText. W propsach będzie długość tego loremText zapisana. Teraz pora włączyć obsługę stanu:
function LoremItem(props) {
const loremText = "Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure veniam sed est rem quam ab a quis? Ipsum in, dolore ad eligendi ex maxime consequuntur quasi laudantium omnis? Dignissimos, ab!"
const [text,] = useState('');
return (
<p>{text}</p>
)
}
Już zaczyna to jakoś wyglądać. setText nie będziemy używać, więc je pominęliśmy.
Teraz chcemy użyć callback w useState, aby przekazać wartość początkową, wyliczaną za pomocą props.num. Tyle razy ma powtórzyć loremText, ile razy mamy w props.num:
function LoremItem(props) {
const loremText = "Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure veniam sed est rem quam ab a quis? Ipsum in, dolore ad eligendi ex maxime consequuntur quasi laudantium omnis? Dignissimos, ab!"
const [text,] = useState(() => {
let num = props.num;
let text = '';
while(num) {
text += loremText;
num--;
}
return text;
});
return (
<p>{text}</p>
)
}
Używamy tego tak (w App.js w returnie funkcji App):
<LoremItem num={3}/>
Przyda nam się do następnego przykładu.
Instalowanie zewnętrznych bibliotek – Scroll Into View
Nasz App.js powinien teraz wyglądać tak:
import './App.css';
import React, { useState} from 'react';
function LoremItem(props) {
const loremText = "Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure veniam sed est rem quam ab a quis? Ipsum in, dolore ad eligendi ex maxime consequuntur quasi laudantium omnis? Dignissimos, ab!"
const [text,] = useState(() => {
let num = props.num;
let text = '';
while(num) {
text += loremText;
num--;
}
return text;
});
return (
<p>{text}</p>
)
}
function App() {
return (
<div className="App">
<LoremItem num={9}/>
<LoremItem num={7}/>
<LoremItem num={7}/>
<LoremItem num={9}/>
<LoremItem num={12}/>
<LoremItem num={9}/>
<LoremItem num={12}/>
</div>
);
}
export default App;
Zainstalujemy sobie biblioteczkę ScrollIntoView. Jeżeli mamy włączony npm start, to go wyłączmy (CTRL+C oraz y, enter) i odpalmy poniższą komendę:
npm i --save react-scroll-into-view
Teraz dokonujemy potrzebnego importu:
import ScrollIntoView from 'react-scroll-into-view';
Dodajemy guzik do scrollowania na sam dół opleciony wrapperem, który zaimportowaliśmy:
<div className="App">
<ScrollIntoView selector="#footer">
<button>Jump to bottom</button>
</ScrollIntoView>
Teraz gdzieś na samym dole (przed zamykającym divem) dodamy element, o id „footer”, do którego będziemy mieli element płynnego scrollowania po wciśnięciu przycisku:
<div id="footer">Scroll target element</div>
</div>
);
Teraz po wykonaniu npm start możemy zobaczyć akcję płynnego przechodzenia do stopki po wciśnięciu naszego przycisku. A czy dalibyśmy radę zrobić odwrotność tego? Płynne przejście ze stopki do góry?
Po pierwsze, musimy nadać id naszemu przyciskowi:
<ScrollIntoView selector="#footer" >
<button id="up">Jump to bottom</button>
</ScrollIntoView>
Po drugie, użyć wrappera na dole, z odpowiednim atrybutem selector:
<ScrollIntoView selector="#up">
<div id="footer">Scroll target element</div>
</ScrollIntoView>
</div>
);
Działa. W przyszłości często będziemy korzystać z różnych zewnętrznych bibliotek Reacta.