(październik 2025)
Jestem programistą z 15-letnim doświadczeniem. Programuję głównie w środowisku JS w tym w (Node, Nest.js, React, React Native) oraz w Python.
Prowadzę firmę No Input Signal. Pracujemy między innymi dla Polskiego Radia, Polskiej Rady Biznesu, PKP Infomatyka, Orange Polska. Jestem wykładowcą w SWPS.
What is React? Short introduction and a piece of history
Getting started with React. Review of available boilerplates and toolkits (webpack, vite, babel, jamstack apps etc.)
React Dev Tools
Thorough explanation of basics: virtual DOM (fiber nodes, reconciliation), JSX, one way data binding
React components
Props in React, explanation of “unidirectional data flow”
React state
Introduction to hooks: useState and useEffect hooks
Composition in React
React jest JavaScriptową biblioteką
stworzoną przez Facebooka.
Służy do tworzenia frontendowych interfejsów użytkownika.
Duże, aktywne środowisko – łatwo znaleźć wsparcie
Ma wsparcie dużej firmy Facebook
Pozwala na dzielenie większej aplikacji na mniejsze fragmenty, dzięki własnym komponentom.
Dzięki takim rozwiązaniom jak np. wirtualny DOM,
jest bardzo wydajną biblioteką.
"In the essence framework is solving structural and architectural problems on the code level."
"React does not solve any structural or architectural problems on the app level. It provides us with a set of methods for better handling of front-end."
Manipulacja elementami DOM za pomocą JS jest bardzo wolna i nie wydajna. Jednym z głównych powodów jest fakt, że aktualizujemy DOM w zakresie o wiele większym, niż niezbędny.
Wirtualny DOM (VDOM) to reprezentacja drzewa DOM przechowywana w pamięci i synchronizowana z nim za pomocą biblioteki ReactDOM. Proces ten nosi nazwę reconciliation.
UWAGA: Shadow DOM to nie to samo co Virtual DOM
Manipulacja elementami DOM za pomocą JS jest bardzo wolna i nie wydajna. Jednym z głównych powodów jest fakt, że aktualizujemy DOM w zakresie o wiele większym, niż niezbędny.
Wirtualny DOM (VDOM) to reprezentacja drzewa DOM przechowywana w pamięci i synchronizowana z nim za pomocą biblioteki ReactDOM. Proces ten nosi nazwę reconciliation.
UWAGA: Shadow DOM to nie to samo co Virtual DOM
porozmawiamy o tym szerzej trochę później ;)
To rozszerzenie języka JavaScript o tagi podobne do tych, które stosujemy w HTML
const element = <h1>Hello, world!</h1>;Za chwilę porozmawiamy o tym szerzej :)
Przykład:
1. Edytor: https://www.jetbrains.com/webstorm/
2. Node: https://nodejs.org/en/
3. Chrome: https://www.google.pl/chrome/browser/desktop/index.html
4. Terminal/Git bash: https://gitforwindows.org/
https://marketplace.visualstudio.com/items?itemName=jawandarajbir.react-vscode-extension-pack
Współeczny React obudowany jest narzędzia, aplikacje i frameworki automatyzujące prace z tą biblioteką. Mamy całkiem sporo opcji by zacząć:
Aplikacja React nie składa się z jednego pliku JS, tylko z setek modułów (.js, .jsx, .ts, .css, obrazki, fonty).
Przeglądarka rozumie tylko „goły” JavaScript i nie wie, jak importować pliki inne niż JS/ESM.
Gdybyśmy wrzucili wszystkie pliki osobno, ładowanie strony byłoby wolne i nieoptymalne.
Potrzebujemy narzędzia, które połączy to wszystko w zoptymalizowaną paczkę.
Module bundler to narzędzie, które:
zbiera wszystkie pliki projektu (JS, TS, CSS, grafiki itp.),
rozwiązuje zależności między nimi (import/require),
konwertuje je do formatu zrozumiałego dla przeglądarki,
łączy w jeden lub kilka zoptymalizowanych plików (bundle).
Transpilacja – np. z TypeScript → JavaScript, JSX → JS.
Tree-shaking – usuwa nieużywany kod.
Code splitting – dzieli aplikację na mniejsze części ładowane „na żądanie”.
Minifikacja – zmniejsza rozmiar kodu (usuwa spacje, zmienia nazwy zmiennych).
Hot Module Replacement (HMR) – odświeża tylko zmieniony moduł w dev, bez przeładowania strony.
Webpack – najstarszy i najbardziej rozbudowany, używany np. w Next.js.
Rollup – preferowany do bibliotek, bardzo czysty output.
Vite (z Rollupem i esbuild) – nowoczesny, superszybki bundler dla dev i produkcji.
Parcel – prostszy, działa bez konfiguracji.
Główne 2 metody, dostarczane przez moduł react-dom to:
const root = ReactDOM.createRoot(element do
której będziemy wstrzykiwać aplikację);
root.render(element, który wstrzykujemy)Renderujemy (wstawiamy do rzeczywistego drzewa DOM) elementy, które chcemy wyświetlić.
<br /> lub <image src="src/logo.png" /><div style={{color: 'red' }}/>
<div height="0"/>JSX
<p id="logo">Hello, World</p>JS
React.createElement(
"p",
{id: "logo"},
"Hello, World"
);Wyrażeniem JSX jest dowolny kod JavaScript, który coś zwraca np.:
<div>{ 4 + 3 }</div>const name = 'Jan';
const count = name.length;
<div>Twoje imię ma { count } znaków</div>const name = 'Jan';
const str = <p>Hello,{ name }</p>
<div>{ str }</div>const name = 'Jan';
const cmp = <Hello>Hello,{ name }</Hello>
<div>{ cmp }</div>Jeszcze nie wiemy co to komponenty, ale wkrótce się wszystko wyjaśni.
<div contentEditable={ true }>Sample<∕div>const style = {
color: 'blue',
backgroundColor: 'green'
};
<div style={style}><∕div>return <ul>
<li key="1">Element 1</li>
<li key="1">Element 2</li>
<li key="3">Element 3</li>
</ul>;
return [
<li key="1">Element 1</li>,
<li key="1">Element 2</li>,
<li key="3">Element 3</li>,
];
React 15.x - element główny
React 18.x - tablica elementów
return <React.Fragment>
<li key="1">Element 1</li>,
<li key="1">Element 2</li>,
<li key="3">Element 3</li>,
</React.Fragment>;
return <>
<li key="1">Element 1</li>,
<li key="1">Element 2</li>,
<li key="3">Element 3</li>,
</>;
React 18.x - Fragment
React 18.x - Fragment-
skrócona wersja
const tab = [1,2,3];
return <ul>
{
tab.map((el, i) => <li key={i}>{el+2}</li>)
}
</ul>;
// <li key="1">3</li>,
// <li key="1">4</li>,
// <li key="3">5</li>,
Elementy JSX możemy wyrenderować z tablicy poprawnych elementów JSX lub komponentów.
Kiedy zwracamy kolekcję musimy każdemu elementowi nadawać unikalny klucz (key). React wykorzystuje klucze w celach optymalizacyjnych.
Najważniejsze heurystyki Reacta:
Jeśli typ elementu (np. <div> vs <span> lub ComponentA vs ComponentB) się zmienia, React usuwa stary element i tworzy nowy.
Jeśli element ma ten sam typ i ten sam klucz (key), React przyjmuje, że to ten sam element i tylko aktualizuje jego właściwości (props, state).
W listach (np. przy .map()) klucze (key) pomagają Reactowi rozpoznać, które elementy zostały dodane, usunięte lub przesunięte.
Wydajność algorytmów Reacta wynika z "wiedzy" w jaki sposób najczęściej postępują programiści (algorytmy heurystyczne)
Poznajmy kilka zasad zgodnie z którymi React dokonuje zmian w DOM.
Powinniśmy wskazywać, które elementy są stabilne pomiędzy zmianami za pomocą kluczy.
<ul>
<li> Jan </li>
<li> Ala </li>
</ul>
//Dodajemy element na koniec
//Nie ma problemu
<ul>
<li> Jan </li>
<li> Ala </li>
<li> Ela </li>
</ul>Dlaczego?
<ul>
<li> Jan </li>
<li> Ala </li>
</ul>
//Dodajemy element na początek
//Jest problem
<ul>
<li> Ola </li>
<li> Jan </li>
<li> Ala </li>
</ul><ul>
<li key="89237284"> Jan </li>
<li key="81928284"> Ala </li>
</ul>
//Dodajemy element na początek
//Jest problem
<ul>
<li key="77284737"> Ola </li>
<li key="89237284"> Jan </li>
<li key="81928284"> Ala </li>
</ul>Klucze pomagają Reactowi zidentyfikować, które elementy uległy zmianie, zostały dodane lub usunięte.
const App = () => {
const animals = ["dog", "cat", "horse"];
const liElements = animals.map( el => {
return <li> {el} </li>
})
return liElements;
}Jeśli generujemy elementy w tablicy, React ostrzega nas w konsoli o problemie.
const App = () => {
const animals = ["dog", "cat", "horse"];
const liElements = animals.map( (el,i) => {
return <li key={i}> {el} </li>
})
return <ul>{liElements}</ul>
}musimy dodać klucze do każdego elementu listy .
Moglibyśmy wykorzystać index np.:
ale ...
React odradza używania zwykłych indeksów jako kluczy.
Jeśli klucz jest indeksem, zmiana kolejności elementu zmienia ten indeks - co powoduje, że nie jest on stabilny/tożsamy z danym elementem!
Może to powodować problemy podczas działania aplikacji np. usuwanie nie tego elementu co trzeba.
Najlepiej skorzystać z id danego elementu.
<div>
<h1> Hello </h1>
<Counter />
</div>
//Jeżeli zmienimy element główny to
//wszystko jest montowane od nowa
<section>
<h1> Hello </h1>
<Counter />
</section>Jeżeli zmieni się element główny to jego dzieci również się zmieniają
Możemy wskazać, które elementy są stabilne pomiędzy zmianami dzięki atrybutowi key w listach.
Zrób zadania z sekcji JSX -> Zagnieżdżanie
Komponent to pojedynczy element interfejsu użytkownika, odpowiedzialny za konkretną część aplikacji. Składa się z elementów i/lub innych komponentów.
Komponent jest funkcją.
const Hello = () => {
return <h1>Hello World </h1>
}
//Wywołujemy
<Hello />class Hello extends React.Component {
render() {
return <h1> Hello </h1>
}
}
//Wywołujemy
<Hello />props to sposób na przekazywanie informacji komponentom tak samo jak atrybuty przekazują je elementom.
<Hi name="Zygmunt" />
//Zapis funkcyjny
const Hi = (props) => <h1>Hi, {props.name}</h1>
//Zapis klasowy
class Hi extends React.Component {
render() {
return <h1>Hi, {this.props.name}</h1>;
}
}<Hi numbers={[1,2,3]} />
//Zapis funkcyjny
const Hi = (props) => <div> Hi,
{props.numbers.map( e => <span> {e} </span> )}
</div>
//Zapis klasowy
class Hi extends React.Component {
render() {
return <div> Hi,
{this.props.numbers.map( e => <span> {e} </span> )}
</div>
}
}const pureFunc = (a, b) => {
return a + b;
}
pureFunc(2,2) // 4;
pureFunc(2,3) // 5
const dirtyFunc = () => {
return Math.random();
}
dirtyFunc() //0.5211613377746926
dirtyFunc() //0.4766865051280922Czym jest pure function?
W kontekście Reacta i Reduxa wszelkie komponenty i funkcje powinny być "pure".
Komponent nie powinien modyfikować danych, które otrzymuje.
class KomponentB extends React.Component {
render() {
return <div> {this.props.name} </div>
}
}
class KomponentA extends React.Component {
render() {
return <KomponentB name={this.props.name} />
}
}
ReactDOM.render(
<KomponentA name="Ala"/>,
document.getElementById("root")
)Komponenty klasowe
const KomponentB = ({name}) => <div> {name} </div>
const KomponentA = ({name}) => <KomponentB name={name} />
ReactDOM.render(
<KomponentA name="Ala"/>,
document.getElementById("root")
)Komponenty funkcyjne
Niektóre komponenty nie muszą wiedzieć niczego o
swoich dzieciach. Wystarczy, że je tylko wyrenderują
const CrazyBg = (props) => {
return (
<div style={ {backgroundColor: props.color}}>
{props.children}
</div>
);
}
const NotLogged = () => {
return (
<CrazyBg color="green">
<a href="#">Zaloguj</a>
</CrazyBg>
);
}
const Logged = (props) => {
return (
<CrazyBg color="red">
<h3>
Witaj {props.name}
</h3>
<a href="#">Wyloguj</a>
</CrazyBg>
);
}
const App = () => {
return <>
<Logged name="Agata"/>
<NotLogged />
</>
}State (z ang. "stan") jest obiektem przechowującym aktualny, wewnętrzny stan danego komponentu.
Stan możemy zmieniać, ale musimy poznać metody Cyklu Życia Komponentu
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return <h1>
{this.state.counter}
</h1>
}
}Jedną z metod cyklu życia komponentu jeste metoda render(). Odpowiada ona za wyrenderowanie w drzewie DOM odpowiedniego elementu JSX.
Aby móc zarządzać elementami drzewa DOM potrzebujemy mieć pewność, że zostały już "zamontowane". Do tego użyjemy componentDidMount() i to tutaj m.in. będziemy aktualizować DOM.
// Źle
componentDidMount () {
this.state.counter += 1;
}
Nie aktualizujemy state w ten sposób!!!!
Korzystamy z setState()
//Dobrze
componentDidMount () {
this.setState({
counter: this.state.counter + 1
})
}
UWAGA: Funkcja setState() nie zmienia stanu od razu tzn. działa asynchronicznie. React grupuje kilka wywołań setState w jedno wywołanie zwiększając tym samym wydajność aplikacji.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
componentDidMount() {
this.id = setInterval( () => {
this.setState({
counter: this.state.counter + 1
})
})
}
render() {
return <h1>
{this.state.counter}
</h1>
}
}class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
componentDidMount() {
this.id = setInterval( () => {
this.setState({
counter: this.state.counter + 1
})
})
}
componentWillUnmount() {
clearInterval(this.id);
}
render() {
return <h1>
{this.state.counter}
</h1>
}
}Dodajemy sprzątanie
Montowanie - kiedy komponent jest tworzony i wstrzykiwany do DOM
- constructor()
- render()
- componentDidMount()
Aktualizacja - kiedy komponent jest przerenderowywany
- shouldComponentUpdate()
- render()
- componentDidUpdate()
Odmontowywanie - kiedy komponent jest usuwany z DOM.
- componentWillUnmount()
(Hooks)
Hooki to dodatek do Reacta 16.8. Wcześniej nie istniała taka funkcjonalność. Zamiast tego korzystaliśmy z klas
(Dzięki Hooks możemy używać state i metod cyklu życia komponentu bez użycia klas.)
Hook to funkcja, dzięki której możemy "zahaczyć" ją w wewnętrzne mechanizmy Reacta.
import React, { useState } from 'react';
const Counter2 => () {
const [counter, setCounter] = useState(0);
return (
<div>
Hook {counter}
</div>
)
}Jako parametr przyjmuje wartość początkową dla zmiennej counter. Zwraca parę: aktualną wartość stanu oraz funkcje, którą będzie go aktualizować.
import React, { useState } from 'react';
const Counter2 => () {
const [counter, setCounter] = useState(0);
const [name, setName] = useState("Ala");
const [animal, setAnimal] = useState("Dog");
return (
<div>
Hook {counter}
<h1>{name} {animal} </h1>
</div>
)
}Kilka wartości naraz
//Zwraca parę
const [animal, setAnimal] = useState('Reksio');
//Inaczej
var animalState = useState('Reksio'); // Zwraca parę
var animal = animalState[0]; // Pierwszy element pary
var setAnimal = animalState[1]; // Drugi element paryDzięki useEffect komponent może wykonać jakiś efekt uboczny np:
useEffect to połączenie: componentDidMount, componentDidUpdate i componentWillUnmount
import React, { useState, useEffect } from 'react';
const Counter2 = () => {
useEffect(() => {
const id = setInterval(() => {
setCounter(counter => counter+1)
}, 1000)
})
return (
<div>
Hook {counter}
</div>
)
}Tutaj useEffect jest wywoływane za każdym razem gdy zmieni się komponent.
import React, { useState, useEffect } from 'react';
const Counter2 = () => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCounter(counter => counter+1)
}, 1000)
}, [])
return (
<div>
Hook {counter}
</div>
)
}Teraz useEffect jest wywołane tylko raz, na początku.
Do useEffect przekazujemy drugi argument w postaci pustej tablicy
useEffect(() => {
const id = setInterval(() => {
console.log(counter) // zawsze 0
setCounter(counter => counter+1)
}, 1000)
}, [])
Jeśli chcemy mieć dostęp do poprzedniej wartości stanu musimy skorzystać z funkcji callback przekazanej do setCounter. Powód?
Mamy dostęp do zmiennej counter tylko w pierwszym renderowaniu, ponieważ useEffect nie jest wywoływany po raz drugi. Counter jest zawsze równy 0 przy wywoływaniu setInterval.
useEffect(() => {
const id = setInterval(() => {
setCounter(counter => counter+1)
}, 1000)
return () => clearInterval(id);
}, [])
A jeśli chcielibyśmy zaktualizować komponent tylko wtedy, gdy zmieni się jakaś konkretna wartość?
import React, { useState, useEffect } from 'react';
const App = () => {
const [title, setTitle] = useState("Empty")
useEffect(() => {
document.title = title
}, [title])
return <input
type="text"
onChange={e => setTitle(e.target.value)}
/>
}Korzystamy z tablicy przekazanej jako drugi argument.
Uruchamiamy ponownie efekt, gdy zmieni się wartość title
Jeśli chcielibyśmy stworzyć zmienną, która będzie widoczna w całym komponencie i będzie niezależna od cyklu życia komponentu możemy skorzystać z hooka useRef()
useRef() zwraca nam mutowalny obiekt (referencję) z polem current. Pole to możemy podczas tworzenia obiektu zainicjalizować.
const ref = useRef(initialValue);
useEffect(() => {
ref.current = newValue
})Do zapamiętania:
- wartość referencji stworzonej za pomocą useRef() jest stała pomiędzy kolejnymi renderowaniami komponentu
- Aktualizacja referencji nie powoduje ponownego renderowania komponentu
const App = () => {
const [counter, setCounter] = useState(0)
const timer = useRef();
useEffect(()=> {
timer.current = setInterval(()=>{
setCounter(counter => {
if(counter >=5 )
clearInterval(timer.current)
return counter + 1
})
}, 1000)
return () => clearInterval(timer.current)
}, [])
return <div> Counter: {counter} </div>
}const App = () => {
const [counter, setCounter] = useState(0)
const timer = {current: null} // za kazdym razem nowy obiekt
console.log("ja się renderuje!")
useEffect(()=> {
timer.current = setInterval(()=>{
setCounter(counter => {
if(counter >=5 )
clearInterval(timer.current)
return counter + 1
})
}, 1000)
return () => clearInterval(timer.current)
}, [])
return <div> Counter: {counter} </div>
}// ...
let timer = useRef();
useEffect(()=> {
//tak też nie. Wyrzucamy całą referencję
//zastepując ją zwykła zmienną
timer = setInterval(()=>{
setCounter(counter => {
if(counter >=5 ) clearInterval(timer)
return counter + 1
})
}, 1000)
return () => clearInterval(timer)
}, [])
// ...// timer jest obiektem, który jest
// przechowywany przez mechanizmy Reacta
const timer = useRef();
// Tutaj aktualizuje tylko
// własność tego obiektu.
timer.current = 1234; useMemo
useMemo pozwala nam zapamiętać niektóre operacje po to aby nie multiplikować obliczeń przy każdym renderowaniu komponentu.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);Powyższe obliczenie wykona się tylko wtedy gdy zmienią się wartości w tablicy zależności.
React.memo()
React memo pozwala sprawdzić czy render komponentu jest konieczny na podstawie zadanych propsów.
const MyComponent = React.memo(function MyComponent(props) {
/* render using props */
} /* optional comaparison function (prevProps, nextProps) => { ... } */);React.memo()
Uwaga! Samo porównywanie propsów pomiędzy renderami jest operacją kosztowną z punktu widzenia pamięci.
useCallback
useCallback – pozwala zapobiec rerenderowaniu komponentów do których przekazujemy callbacki.
{} === {} // FalsyuseCallback
Przyjrzyjmy się poniższemu przykładowi w którym callback przekazywany jest do komponentu dziecka.
...
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
return <Child callback={memoizedCallback} />
...
function Child = React.memo(({callback}) => {
...
})forwardRef
Forward ref pozwala przekazać referencję do komponentu dziecka i zarządzać nią z poziomu rodzica.
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
// you may now access the ref of a button from parent perspective
// for example ref.current.focus()
<FancyButton ref={ref}>Click me!</FancyButton>;useReducer
Another pretty useful hook is useReducer. Not that popular but definitely worth mentioning. Basically, it's the same as useState but it allows us to deal with complicated and nested states. It also allows us to carry out complicated mutations of the state.
const [state, dispatch] = useReducer(reducer, initialArg, init);useReducer
useReducer to zaawansowany hook pozwalający na zarządzanie skomplikowanymi stanami.
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + action.payload};
case 'decrement':
return {count: state.count - action.payload};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}useLayoutEffect
useLayoutEffect to hook identyczny z useEffect za wyjątkiem jednej funkcjonalności. Jest on synhroniczny w odróżnieniu od useEffect. Użyjemy go w sytuacji, kiedy potrzebujemy bezpośrednio manipulować DOM.
Przykłady użycia:
https://greensock.com/react/
useDefferedValue
Hook useDefferedValue to nowość, która pojawiła się w wersji 18 React.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}useDefferedValue
Funkcjonalność ta pozwala nam priorytetyzować i odsuwać w czasie poszczególne updatey stanu. Dokumentacja React mówi tutaj o `trybie konkurencyjnym`
The algorithm used in achieving this magic is called the Time Slicing algorithm. It allows React to break down the rendering into smaller and prioritized chunks called fiber nodes, which are then scheduled based on priority and expiration time. The most critical part of the user interfaces is the highest priority fiber nodes, such as your buttons and menus, which need to be updated quickly, unlike the lowest priority fiber nodes, such as a different tab or a part of the page are not visible yet. This is done to improve the user experience and interface performance so that the user can continue interacting with the page while rendering is in progress.
useTransition
Jeszcze jednym przykładem trybu konkurencyjnego jest hook useTransition. Transion pozwoli nam przeprowadzić update stanu w sposób asynchroniczny.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
Możemy również tworzyć własne hooki. Z punktu widzenia JavaScript to funkcje, które używają wewnątrz klasycznych hooków i zazwyczaj zwracają wartości stanów.
source: Read ⬇️ https://reactjs.org/docs/hooks-custom.html
Przykład:
Nazwy zdarzeń w React są takie same jak w JavaScript z jedną małą różnicą, że na początku dodajemy słówko "on"
| onClick | onChange | onMouseEnter |
|---|---|---|
| onMouseLeave | onKeyPress | onKeyDown |
| onFocus | onBlur | onSubmit |
Przykładowe:
https://reactjs.org/docs/events.html
const ClickTest = () => {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(counter + 1)
}
return (
<div>
<h1>{counter}</h1>
<button onClick={handleClick}>
Kliknij mnie
</button>
</div>
)
}onClick
const ClickTest = () => {
const [counter, setCounter] = useState(0);
const handleClick = (e, name) => {
console.log(e, name)
setCounter(counter + 1)
}
return (
<div>
<h1>{counter}</h1>
<button onClick={ e => handleClick(e, "Jan")}>
Kliknij mnie
</button>
</div>
)
}
const Hello = (props) => {
return <div>
Hello {props.name}
</div>
}
const Counter = (props) => {
return <div>
{props.num}
</div>
}
const App = () => (
<div>
<Hello name="Jan"/>
<Hello name="Giorgio" />
<Counter num="1" />
</div>
);App
Hello
Counter
const Hello = (props) => {
return <div>
Hello {props.name}
</div>
}
const Counter = (props) => {
const handleClick = () => {
props.add();
}
return <div> {props.num}
<button onClick={handleClick}> Add </button>
</div>
}
const App = () => {
const [counter, setCounter] = useState(0);
const addNum = () => {
setCounter(counter + 1)
}
return <div>
<Hello name="Jan" />
<Hello name="Giorgio" />
<Counter num={counter}
add={addNum}
/>
</div>
}const App = () => {
const [isLogged, setIsLogged] = useState(false);
if(isLogged) {
return <h1> Witaj </h1>
} else {
return <span>Zaloguj</span>
}
}const App = () => {
const [isLogged, setIsLogged] = useState(false);
const el = isLogged ?
<h1>Witaj</h1> :
<span>Zaloguj</span>
return el;
}operator trójargumentowy
const App = () => {
const [msgs, setMsgs] = useState([]);
return msgs.length > 0 &&
<h1>Masz {msgs.length}
nieprzeczytanych wiadomości
</h1>
}operator logiczny &&
const App = () => {
return null;
}Jeśli nie chcemy wyświetlić komponentu możemy zwrócić null lub po prostu false
const App = () => {
const [value, setValue] = useState("");
const handleChange = (event) => {
setValue(event.target.value)
}
return <form>
<label>
Email:
<input type="text"
value={value}
onChange={handleChange}/>
</label>
</form>
}const App = () => {
const [value, setValue] = useState("");
const handleChange = (event) => {
setValue(event.target.value)
}
const handleSubmit = () => {
//walidacja, wysyłanie
}
return <form onSubmit={handleSubmit}>
<label>
Wiadomość:
<textarea type="text"
value={value}
onChange={handleChange}/>
</label>
<input type="submit" value="wyślij" />
</form>
}<form onSubmit={handleSubmit}>
<label>
Wybierz ulubionego zwierzaka:
<select value={value}
onChange={handleChange}>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="shrimp">Shrimp</option>
<option value="goose">Goose</option>
</select>
</label>
<input type="submit" value="Wybierz" />
</form>const App = () => {
//.....
return <form>
<label>
Email:
<input type="text"
value={values.email}
name="email"
onChange={handleChange}/>
</label>
<label>
Password:
<input type="password"
value={values.password}
name="password"
onChange={handleChange}/>
</label>
</form>
}Ustawiamy właściwość name
const App = () => {
const [values, setValues] = useState({email: "", password: ""});
const handleChange = (event) => {
const name = event.target.name;
setValues({
...values,
[name]: event.target.value
})
}
return //...
}useRef()
Używamy zazwyczaj do przechowywania odniesień do elementów DOM lub do przechowywania prostych typów i obiektów.
const myRef = useRef(null); <input type="text"
ref={myRef}
onChange={handleChange}/>useRef()
Pamiętaj, że odwołujemy się do elementów DOM HTML, a nie do komponentów React.
Aby dostać się do referencji wskazującej na element korzystamy z pola "current"
console.log(myRef.current)useRef()
const App = () => {
const myRef = useRef(null);
const handleChange = (event) => {
console.log(myRef.current.value)
}
return <form>
<label>
Email:
<input type="text"
ref={myRef}
onChange={handleChange}/>
</label>
</form>
}Aplikacje React szybko rosną – setki komponentów, wiele widoków.
Gdybyśmy wrzucili wszystko w jeden plik JS (bundle), użytkownik musiałby ściągnąć cały kod, nawet jeśli korzysta tylko z jednej podstrony.
To oznacza: wolniejsze ładowanie strony i gorsze UX.
React.lazy + Suspense – wbudowany mechanizm do ładowania komponentów na żądanie.
dynamic import (import()) – standard JS, który wspiera bundler (Webpack, Vite).
Next.js dynamic imports (next/dynamic) – ułatwiają code splitting w aplikacjach SSR.
import React, { Suspense } from "react";
// Ładujemy komponent dopiero, gdy będzie potrzebny
const Dashboard = React.lazy(() => import("./Dashboard"));
function App() {
return (
<div>
<h1>Moja aplikacja</h1>
<Suspense fallback={<div>Ładowanie...</div>}>
<Dashboard />
</Suspense>
</div>
);
}import React, { Suspense } from "react";
// Ładujemy komponent dopiero, gdy będzie potrzebny
const Dashboard = React.lazy(() => import("./Dashboard"));
function App() {
return (
<div>
<h1>Moja aplikacja</h1>
<Suspense fallback={<div>Ładowanie...</div>}>
<Dashboard />
</Suspense>
</div>
);
}Render Props to wzorzec projektowy w React, w którym komponent przyjmuje funkcję jako props. Ta funkcja zwraca elementy Reacta i decyduje, co ma się wyrenderować.
Inaczej mówiąc:
Podstawowa idea jest jednak jeszcze prostsza. Propsy nie renderują się tak jak komponenty dzieci. Brzmi dziwnie ale w praktyce oznacza, że powinniśmy możliwie zawsze używać props children
To biblioteka do nawigacji między stronami (routami) w aplikacji React bez przeładowywania całej strony. Dzięki niej możesz mieć tzw. Single Page Application (SPA), w której adres URL się zmienia, ale aplikacja wciąż działa w ramach jednej strony.
Router – „kontener” dla całej aplikacji, np. BrowserRouter.
Routes – zbiór wszystkich ścieżek, jakie obsługuje aplikacja.
Route – pojedyncza reguła, która mówi: dla tego URL pokaż ten komponent.
Link / NavLink – zamienniki <a> do nawigacji wewnętrznej (nie odświeżają strony).
useNavigate – hook, żeby programowo przenosić użytkownika do innego adresu.
useParams – hook do odczytania parametrów z URL (np. /user/5).
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function Home() {
return <h2>Strona główna</h2>;
}
function About() {
return <h2>O nas</h2>;
}
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link> |{" "}
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
export default App;<Route path="/user/:id" element={<User />} />
function User() {
const { id } = useParams();
return <h2>Profil użytkownika: {id}</h2>;
}import { useNavigate } from "react-router-dom";
function Dashboard() {
const navigate = useNavigate();
return (
<button onClick={() => navigate("/about")}>
Idź do About
</button>
);
}BrowserRouter – standardowy, używa HTML5 history API.
HashRouter – używa # w URL (np. /app#/about), przydatny przy hostingu na GitHub Pages.
MemoryRouter – trzyma historię w pamięci (przydatny w testach).
Fetch API to narzędzie do komunikowania się z różnymi źródłami danych. Różnica między nim a XMLHttpRequest polega nam tym, że Fetch korzysta z Promises, dzięki temu możemy uniknąć tzw. callback hell.
fetch('https://api.kanye.rest/');fetch() zwraca obiekt typu Promise. Aby odebrać dane możemy użyć metody Promise.then().
fetch('https://api.kanye.rest/')
.then( resp => {
console.log( resp );
});Aby uzyskać z niego odpowiedź w formacie json musimy użyć: Response. json()
Metoda ta zwróci kolejny obiekt typu Promise, który tym razem przy spełnieniu zwróci nam dane w wersji jsona.
fetch('https://api.kanye.rest/')
.then( resp => resp.json())
.then( data => {
console.log(data)
})fetch('https://api.kanye.rest/')
.then( resp => resp.json() )
.then( data => {
console.log(data)
})
.catch( error => {
console.log(error)
})fetch(url, {
headers : {
'X-My-Header' : 'test'
}
});Jeżeli musimy przesłać nagłówek to:
fetch(url, {
method : 'POST'
});const user = {
name: "Aga"
};
fetch(url, {
method : 'POST',
body: JSON.stringify( user )
}); useEffect(() => {
fetch('https://api.kanye.rest/')
.then(r=>r.json())
.then(response => {
setData(response)
})
}, [])fetch używamy w komponentach klasowych w funkcji componentDidMount natomiast w funkcyjnych w useEffect.
Po otrzymaniu danych zmieniamy state.
Async/ Await zapewnia nam przyjemniejszą składnię w przypadku zagmatwanych scenariuszy. Dzięki Async/ Await kod wygląda na synchroniczny.
const url = 'https://api.kanye.rest/';
useEffect(() => {
(async () => {
const response = await fetch(url);
const json = await response.json();
setData(json);
})();
}, []);Global state – czyli stan globalny pozwala utrzymywać stan nie tylko dla jednego komponentu ale dla określonego węzła lub nawet całej aplikacji.
Flux to angielskie słowo oznaczające strumień lub przepływ. Jest to też nazwa architektury aplikacji zaproponowanej przez Facebooka. Gigant twierdzi zresztą, że używa Fluksa do budowania swoich aplikacji, a sam koncept stał się ostatnio niezwykle popularny. Podstawą Fluksa jest jeden wzorzec projektowy i jedno proste założenie, dlatego można zacząć z niego korzystać z niezwykłą wręcz łatwością, bez konieczności instalowania dodatkowych bibliotek czy frameworków.
TypeOfWeb
Flux Architecture – wzorzec projektowy w którym dane mogą przepływać tylko w jednym kierunku.
Parts of the Flux Architecture
The Facebook Chat problem
React Context API
React Context API – standardowy implementacja architektury flux w React.
Context API zostało wprowadzone w React 16. Więc jest to całkiem nowa funkcjonalność.
source: https://www.loginradius.com/blog/engineering/react-context-api/
Read: https://reactjs.org/docs/context.html#api
What is React 16 context API and why should you care?
https://ipraveen.medium.com/react-basic-how-react-16-context-api-work-7257591589fc
React Context
Tworzenie kontekstu
import React from 'react';
export const TestContext = React.createContext({
someState: null,
setSomeState: () => null
})Context Provider
Provider określa zakres dla kontekstu – wszystko co będzie się znajdowało wewnątrz będzie nim objęte.
const SomeComponent = () => {
const [someState, setSomeState] = useState([])
return <TestContext.Provider value={{someState, setSomeState}}>
<Child />
</TestContext.Provider>
}Context Consumer
Konsument kontekstu – pozwala wykorzystać jego zasoby w określonym komponencie.
const Child = () => {
return (
<TestContext.Consumer>
{({ someState, setSomeState }) => {
return (
<>
<h1
onClick={() => {
setSomeState("Hello world!!!");
}}
>
{someState}
</h1>
</>
);
}}
</TestContext.Consumer>
);
};Context Consumer
Kontekst zaimplementowany przy pomocy hooka.
const Child = () => {
const { someState, setSomeState } = useContext(TestContext);
return (
<>
<h1
onClick={() => {
setSomeState("Hello world!!!");
}}
>
{someState}
</h1>
</>
);
};Context
Użycie kontekstu wiąże się z pewnym problemem optymalizacyjnym. Jeśli nasz kontekst jest rozbudowany i na przykład ma postać obiektu z kilkoma kluczami, każdy komponent, który korzysta z tego kontekstu poprzez hook useContext będzie renderowany nawet jeśli nie korzysta z całego stanu ale tylko z części, która się nie zmieniła.
Optymalizacja kontekstu
Żeby poradzić sobie z tym problemem Redux korzysta z koncepcji selektorów. W przypadku kontekstu rozwiązania mamy właściwie 4.
Optymalizacja kontekstu
const initialState1 = {
firstName: 'Harry',
};
const initialState2 = {
familyName: 'Potter',
};
Pierwszy wypadek jest trywialny. Jeśli w naszym kontekście posiadamy dwa klucze i chcemy żeby ich wartości renderowały się niezależnie możemy podzielić kontekst.
Optymalizacja kontekstu
const InnerPersonFirstName = React.memo(({ firstName, dispatch }) => (
<div>
First Name:
<input
value={firstName}
onChange={(event) => {
dispatch({ type: 'setFirstName', firstName: event.target.value });
}}
/>
</div>
);
const PersonFirstName = () => {
const [state, dispatch] = useContext(PersonContext);
return <InnerPersonFirstName firstName={state.firstName} dispatch={dispatch} />;
};Możemy również użyć memo. W taki sposób wymusimy renderowanie w określonych warunkach.
Optymalizacja kontekstu
const PersonFirstName = () => {
const [state, dispatch] = useContext(PersonContext);
const { firstName } = state;
return useMemo(() => {
return (
<div>
First Name:
<input
value={firstName}
onChange={(event) => {
dispatch({ type: 'setFirstName', firstName: event.target.value });
}}
/>
</div>
);
}, [firstName, dispatch]);
};Możemy również wymusić renderowanie tylko w określonych warunkach poprzez hook useMemo.
Optymalizacja kontekstu
//React tracked
const { Provider, useTracked } = createContainer(() => useReducer(reducer, initialState));
const ReactTracked = () => {
return (
<Provider>
<PersonFirstName />
<PersonFamilyName />
</Provider>
);
};
const PersonFirstName = () => {
const [state, dispatch] = useTracked();
return (
<div>
First Name:
<input
value={state.firstName}
onChange={(event) => {
dispatch({ type: 'setFirstName', firstName: event.target.value });
}}
/>
</div>
);
};React-Query
React sam w sobie nie określa jednego sposobu na pobieranie danych z api. Zazwyczaj robimy to przy pomocy fetch api lub bibliotek w rodzaju axios.
Takie podejście wymusza na nas jednak samodzilne zajęcie się taki problemami jak:
Na szczęście mamy biblioteke, która zadania automatyzuje.
React-Query
Zaczynamy od zdefiniowania providera, który wyznaczy kontekst dla zapytań.
import {
QueryClient,
QueryClientProvider,
} from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
// Create a client
const queryClient = new QueryClient()
function App() {
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
...
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}React-Query
useQuery – hook wykonuje zapytania i cache'uje je. queryKey pozwala zidentyfikować zapytanie, queryFn to właściwe zapytanie.
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })
// { isPending, isError, data, error } = queryuseQuery wyposażone jest w mechanizm cachowania i refetchowania. Przy każdym ponownym zamontowaniu hooka z określonym id, focusie okna, przywróceniu łączności bądź w wypadku interwału, useQuery przywróci dane z cache a potem w tle wykona jeszcze raz zapytanie.
React-Query
Wspomniane zachowanie możemy modyfikować:
cacheTime/gcTime: czas przechowywania zapytań w pamięci, może być konfigurowany (domślnie 5 min)
staleTime: czas przechowywania stale response (zapisanych odpowiedzi), domyślnie 0. Oznacza to, że kiedy nowa instancja query zostanie zamontowania react-query w tle pobierze dane aby zaaktualizować query.
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos, staleTime: 1000 })
// { isPending, isError, data, error } = queryReact-Query
W pewnych wypadkach możemy chcieć pobierać dane w pętli w określonych interwałach.
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos, refetchInterval: 1000 })
// { isPending, isError, data, error } = queryReact-Query
Dependant query – niektóre z naszych zapytań będą zależały od innych.
// Get the user
const { data: user } = useQuery({
queryKey: ['user', email],
queryFn: getUserByEmail,
})
const userId = user?.id
// Then get the user's projects
const {
status,
fetchStatus,
data: projects,
} = useQuery({
queryKey: ['projects', userId],
queryFn: getProjectsByUser,
// The query will not execute until the userId exists
enabled: !!userId,
})React-Query
React Query automatycznie podejmie próby refetchowania danych w wypadku niepowodzenia. Liczbę prób można definiować. Jeśli zostanie wyczerpana hook poinformuje o błędzie.
import { useQuery } from '@tanstack/react-query'
// Make a specific query retry a certain number of times
const result = useQuery({
queryKey: ['todos', 1],
queryFn: fetchTodoListPage,
retry: 10, // Will retry failed requests 10 times before displaying an error
retryDelay: 1000,
})React-Query – Mutacje
Wewnątrz komponentu możemy teraz łatwo pobrać dane i je zmutować.
useMutation({
mutationFn: addTodo,
onMutate: (variables) => {
// A mutation is about to happen!
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSuccess: (data, variables, context) => {
// Boom baby!
//
},
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
})React-Query – Mutacje
Po wykonaniu mutacji zazwyczaj będziemy potrzebowali zaktualizować dane w zapytaniach, które korzystały z zapytania przez aktualizacją.
// Get QueryClient from the context
const queryClient = useQueryClient()
useMutation({
mutationFn: addTodo,
onMutate: (variables) => {
// A mutation is about to happen!
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onSuccess: (data, variables, context) => {
// Boom baby!
queryClient.invalidateQueries({ queryKey: ['todos'], { exact: true} })
},
})React-Query – Mutacje
Optymistyczny update – tworząc mutację nie musimy ponownie refetchować danych. Koncpecja optymistycznej mutacji zakłada, że dane które dopiero co wysłaliśmy do serwera mogą zostać przekazane do cache useQuery.
React-Query – Mutacje
const queryClient = useQueryClient()
useMutation({
mutationFn: updateTodo,
// When mutate is called:
onMutate: async (newTodo) => {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
await queryClient.cancelQueries({ queryKey: ['todos'] })
// Snapshot the previous value
const previousTodos = queryClient.getQueryData(['todos'])
// Optimistically update to the new value
queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
// Return a context object with the snapshotted value
return { previousTodos }
},
// If the mutation fails,
// use the context returned from onMutate to roll back
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.previousTodos)
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})React-Query – Prefetching
const prefetchTodos = async () => {
// The results of this query will be cached like a normal query
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
}Prefetching to technika, która pozwala nam pobierać dane dla widoków, które jeszcze nie są renderowane. Np. możemy pobrać dane dla podstrony w momencie kiedy link do niej pojawia się we viewport bądź kiedy użytkownik najeżdza na link kursorem myszy.
React-Query – Mutacje (stany)
A mutation can only be in one of the following states at any given moment:
isIdle or status === 'idle' - The mutation is currently idle or in a fresh/reset stateisLoading or status === 'loading' - The mutation is currently runningisError or status === 'error' - The mutation encountered an errorisSuccess or status === 'success' - The mutation was successful and mutation data is availableBeyond those primary states, more information is available depending on the state of the mutation:
error - If the mutation is in an error state, the error is available via the error property.data - If the mutation is in a success state, the data is available via the data property.Redux
Redux to biblioteka służąca do zarządzania stanem aplikacji przy użyciu eventów nazywanych "akcjami".
Redux jest popularny w środowisku React developerów ale może też być wykorzystywany bez React.
Redux
Store – magazyn danych w którym przechowywany jest aktualny stan aplikacji. Składa się z reducerów.
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())Redux
Reducer – funkacja odpowiedzialna za utrzymywanie i modyfikowanie stanu/store.
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}Redux
Dispatch – metoda służąca do inicjowania zmian w stanie.
store.dispatch({ type: 'counter/increment' })
console.log(store.getState())
// lub
const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
console.log(store.getState())
// {value: 2}Redux
Action – obiekt zawierający klucz type. Actions to po prostu eventy z danymi określającymi zmiany w stanie aplikacji.
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}Redux
Action creator – kreator akcji to nic innego jak funkcja służaca do generowania akcji.
const addTodo = text => {
return {
type: 'todos/todoAdded',
payload: text
}
}Redux
Selectors – funkcja służąca do pobierania specyficznego fragmentu stanu/store.
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)Nowe hooki
useActionState(fn, initialState)
useFormStatus()
useOptimistic(initialValue)
use()
useActionState?To nowy hook do obsługi akcji asynchronicznych (tzw. Actions) – np. wysyłania formularzy, zapisywania danych na serwerze, aktualizacji stanu.
Ułatwia zarządzanie:
stanem błędu,
stanem „pending” (czyli w trakcie wysyłania),
stanem wyniku akcji,
przekazywaniem funkcji action bezpośrednio do formularza (<form action={actionFn}>).
Można go traktować jako **odchudzoną alternatywę dla useReducer + obsługi async`.
useOptimistic?useOptimistic to hook, który pozwala natychmiast zaktualizować UI w reakcji na akcję użytkownika – jeszcze zanim serwer faktycznie zwróci odpowiedź.
To tzw. aktualizacja optymistyczna: zakładamy, że wszystko się uda i od razu pokazujemy wynik, a później React dopasuje stan do realnego rezultatu (sukcesu lub błędu).
Dzięki temu interfejs jest szybszy i bardziej responsywny.
useFormStatus?useFormStatus to hook z biblioteki react-dom, który pozwala sprawdzić aktualny status formularza, w którym znajduje się komponent.
Działa w obrębie <form> – można go używać w dowolnym komponencie zagnieżdżonym w formularzu, bez przekazywania propsów „w dół”.
To przydatne np. do:
dezaktywacji przycisków, gdy formularz jest w trakcie wysyłania,
wyświetlania spinnera / komunikatu „wysyłanie…”,
odczytu danych przesyłanych w formularzu (data).
import { useActionState, useOptimistic } from 'react';
import { useFormStatus } from 'react-dom';
function ChangeName({ currentName, onUpdateName }) {
const [optimisticName, setOptimisticName] = useOptimistic(currentName);
const [error, submitAction, isPending] = useActionState(
async (prev, formData) => {
const newName = formData.get("name");
setOptimisticName(newName);
const result = await updateName(newName);
if (result.error) {
return result.error;
}
onUpdateName(result.updatedName);
return null;
},
null
);
const { pending } = useFormStatus(); // użyte w komponencie potomnym np. przy przycisku
return (
<form action={submitAction}>
<p>Your name is: {optimisticName}</p>
<input name="name" defaultValue={currentName} disabled={isPending} />
<button type="submit" disabled={pending}>Update</button>
{error && <div>{error}</div>}
</form>
);
}// Using pending state from Actions
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
})
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}