Napredni React Hook-ovi: useMemo, useCallback i useRef
Nauči kada i kako koristiti useMemo, useCallback i useRef da eliminišeš nepotrebne re-rendere, stabilizuješ reference i direktno komuniciraš sa DOM-om — paterni optimizacije koje primenjujemo na svakom produkcijskom React i Next.js projektu.
Napredni React Hook-ovi: useMemo, useCallback i useRef
TL;DR — Ključni Uvidi
useMemokešira rezultat skupe kalkulacije između renderauseCallbackkešira referencu na funkciju da se ne bi kreirala iznova pri svakom renderuuseRefčuva mutatabilnu vrednost koja opstaje između rendera bez pokretanja re-rendera- Ovi hook-ovi su alati za optimizaciju — posegni za njima samo kada imaš izmereni problem sa performansama
- Najčešća greška je preterana upotreba, koja dodaje kompleksnost bez ikakve koristi
Zašto je optimizacija performansi važna u React-u
React je po defaultu brz. Ali kako tvoja aplikacija skalira — više komponenti, više podataka, više interaktivnosti — počećeš da primećuješ usporavanje na određenim mestima: filter koji kasni pri kucanju, lista koja se re-renderuje u celosti kada se promeni nepovezani state, callback koji kvari memoizaciju u child komponenti.
U Komunikacija između komponenti: Props i Context API, dotakli smo se memoizacije vrednosti Context-a kako bismo sprečili nepotrebne re-rendere. Ovaj tekst ide dublje: tri hook-a koja ti daju preciznu kontrolu nad React-ovim ponašanjem pri renderovanju.
Prvo razumeti re-rendere
Pre nego što posegneš za hook-ovima za optimizaciju, vredi razumeti kada React re-renderuje komponentu:
- Njen sopstveni state se promeni
- Njen roditelj se re-renderuje (čak i ako se props nije promenio)
- Context koji konzumira se promeni
Tačka 2 je izvor većine problema sa performansama. Svaki put kada se roditelj re-renderuje, sva njegova deca se re-renderuju — po defaultu. U dubokom stablu komponenti, ovo se kaskadno širi.
Hook-ovi za optimizaciju razbijaju ovu kaskadu dajući React-u stabilne reference za poređenje.
useMemo: Keširanje skupih kalkulacija
useMemo memoizuje povratnu vrednost funkcije. React je ponovo izračunava samo kada se jedna od njenih zavisnosti promeni.
import { useMemo, useState } from "react";
interface Product {
id: number;
name: string;
category: string;
price: number;
inStock: boolean;
}
interface ProductTableProps {
products: Product[];
}
export function ProductTable({ products }: ProductTableProps) {
const [search, setSearch] = useState("");
const [showInStockOnly, setShowInStockOnly] = useState(false);
const filteredProducts = useMemo(() => {
return products
.filter((p) => p.name.toLowerCase().includes(search.toLowerCase()))
.filter((p) => (showInStockOnly ? p.inStock : true));
}, [products, search, showInStockOnly]);
return (
<div className="space-y-4">
<div className="flex items-center gap-4">
<input
type="search"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Pretraži proizvode..."
className="rounded-lg border border-gray-300 px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={showInStockOnly}
onChange={(e) => setShowInStockOnly(e.target.checked)}
className="rounded"
/>
Samo na stanju
</label>
</div>
<p className="text-sm text-gray-500">{filteredProducts.length} rezultata</p>
<ul className="divide-y divide-gray-100 rounded-lg border border-gray-200">
{filteredProducts.map((product) => (
<li key={product.id} className="flex items-center justify-between px-4 py-3">
<span className="font-medium">{product.name}</span>
<span className="text-sm text-gray-500">{product.price} RSD</span>
</li>
))}
</ul>
</div>
);
}Bez useMemo, logika filtriranja bi se izvršavala pri svakom pritisku tastera i pri svakoj nepovezanoj promeni state-a u roditelju. Sa useMemo, React preskače ponovni izračun osim ako se products, search ili showInStockOnly zaista promene.
Kada useMemo ima smisla
- Filtriranje ili sortiranje velikih nizova (stotine stavki ili više)
- Kompleksni izvedeni podaci (agregacije, grupisanja, podaci za grafikone)
- Skupe matematičke kalkulacije
Kada useMemo NEMA smisla
- Jednostavan pristup properti-ju ili osnovna aritmetika
- Nizovi ili objekti sa svega nekoliko stavki
- Bilo gde gde nisi izmerio stvarni problem sa performansama
Overhead samog useMemo (čuvanje keširane vrednosti, poređenje zavisnosti) može premašiti cenu ponovnog izračunavanja jeftine operacije.
useCallback: Stabilizacija referenci na funkcije
U JavaScript-u, funkcija definisana unutar tela komponente se kreira iznova pri svakom renderu. Ovo je važno kada tu funkciju prosleđuješ kao prop memoizovanoj child komponenti — nova referenca na funkciju znači da child vidi "promenjeni" prop i re-renderuje se, čime se poništava svrha memoizacije.
useCallback vraća stabilnu referencu na funkciju koja se menja samo kada se promene njene zavisnosti.
import { useCallback, useState, memo } from "react";
interface ActionButtonProps {
onAction: () => void;
label: string;
}
// memo() sprečava re-render ako se props nije promenio
const ActionButton = memo(function ActionButton({ onAction, label }: ActionButtonProps) {
console.log(`Renderovanje: ${label}`);
return (
<button
onClick={onAction}
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
>
{label}
</button>
);
});
export function ProductActions({ productId }: { productId: string }) {
const [status, setStatus] = useState<string | null>(null);
const handleApprove = useCallback(async () => {
await fetch(`/api/products/${productId}/approve`, { method: "POST" });
setStatus("odobreno");
}, [productId]);
const handleReject = useCallback(async () => {
await fetch(`/api/products/${productId}/reject`, { method: "POST" });
setStatus("odbijeno");
}, [productId]);
return (
<div className="flex items-center gap-3">
<ActionButton onAction={handleApprove} label="Odobri" />
<ActionButton onAction={handleReject} label="Odbij" />
{status && (
<span className="text-sm capitalize text-gray-500">Status: {status}</span>
)}
</div>
);
}useCallback i memo funkcionišu kao par. memo sprečava re-render child-a kada su props-i isti — ali samo ako su props-i zaista stabilni. useCallback osigurava da je funkcija kao prop stabilna. Bez jednog, drugi ne pomaže.
useRef: Mutatabilne vrednosti bez re-rendera
useRef kreira mutatabilni kontejner — objekat sa .current propertijem — koji opstaje tokom celog životnog veka komponente. Za razliku od state-a, promena .current ne pokreće re-render.
Ovo čini useRef pravim alatom za dva različita slučaja upotrebe:
Slučaj 1: Direktan pristup DOM elementima
import { useRef, useEffect } from "react";
export function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return (
<input
ref={inputRef}
type="text"
placeholder="Ovo se automatski fokusira..."
className="rounded-lg border border-gray-300 px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
);
}Slučaj 2: Čuvanje vrednosti koje ne treba da okidaju re-render
import { useRef, useEffect, useState } from "react";
export function StopwatchButton() {
const [isRunning, setIsRunning] = useState(false);
const [elapsed, setElapsed] = useState(0);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
if (isRunning) {
intervalRef.current = setInterval(() => {
setElapsed((prev) => prev + 1);
}, 1000);
} else {
if (intervalRef.current) clearInterval(intervalRef.current);
}
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [isRunning]);
return (
<div className="flex items-center gap-4">
<span className="text-2xl font-mono tabular-nums">{elapsed}s</span>
<button
onClick={() => setIsRunning((prev) => !prev)}
className="rounded-lg bg-gray-900 px-4 py-2 text-sm font-medium text-white"
>
{isRunning ? "Zaustavi" : "Pokreni"}
</button>
<button
onClick={() => { setElapsed(0); setIsRunning(false); }}
className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium"
>
Resetuj
</button>
</div>
);
}intervalRef čuva ID intervala bez izazivanja re-rendera kada se promeni. Da smo koristili useState za ID intervala, svaki tick bi izazvao dupli re-render.
Česte Greške i Zamke
1. Preuranjena optimizacija
Ovo je kardinalni greh. Dodavanje useMemo i useCallback svuda "za svaki slučaj" dodaje kognitivni overhead i retko pomaže. Prvo meri profilisanjem u React DevTools, tek onda optimizuj.
2. Nedostajuće zavisnosti
Baš kao useEffect, i useMemo i useCallback imaju nizove zavisnosti. Nedostajuća zavisnost znači da tvoja keširana vrednost postaje zastarela. Drži eslint-plugin-react-hooks uključenim — hvata ovo automatski.
3. Mišljenje da useRef zamenjuje useState
Ako ažuriraš ref i očekuješ da UI odrazi tu promenu, neće se ništa dogoditi — ref-ovi ne okidaju re-rendere. Koristi state za sve što treba biti vidljivo korisniku; koristi ref-ove za vrednosti koje postoje da koordinišu ponašanje iza scene.
4. Kreiranje objekata unutar zavisnosti useMemo
// ❌ `filters` je novi objekat pri svakom renderu — useMemo uvek ponovo izračunava
const filters = { category, minPrice, maxPrice };
const filtered = useMemo(() => applyFilters(products, filters), [products, filters]);
// ✅ Koristi primitivne vrednosti direktno kao zavisnosti
const filtered = useMemo(
() => applyFilters(products, { category, minPrice, maxPrice }),
[products, category, minPrice, maxPrice]
);Okvir za donošenje odluka
Kada se suočimo sa problemom performansi u React komponenti, sledimo ovaj redosled u Atonize-u:
- Prvo izmeri — koristi React DevTools Profiler da potvrdiš koja komponenta je spora i zašto
- Memoizuj komponentu sa
memo()ako se re-renderuje sa identičnim props-ima - Stabilizuj callback props-e sa
useCallbackako kvarememo() - Keširaj skupe kalkulacije sa
useMemoako je neka derivacija zaista skupa - Razmotri arhitekturu — ponekad je pravo rešenje restrukturiranje stabla komponenti, ne dodavanje hook-ova
Za state koji se deli između mnogo komponenti i izaziva rasprostranjene re-rendere, rešenje je često bolja strategija upravljanja state-om umesto više memoizacije. Obrađujemo to u Globalni State Management u 2026. godini.
Često Postavljana Pitanja (FAQ)
Koja je razlika između useMemo i useCallback?
useMemo kešira rezultat pozivanja funkcije — koristi ga za skupe izračunate vrednosti. useCallback kešira samu funkciju — koristi ga kada prosleđuješ callback-ove kao props memoizovanim child komponentama. useCallback(fn, deps) je u suštini ekvivalentno sa useMemo(() => fn, deps).
Da li treba da omotam svaku funkciju u useCallback?
Ne. Koristi useCallback samo kada se funkcija prosleđuje kao prop komponenti omotanoj u memo(), ili kada je navedena kao zavisnost u nizu zavisnosti drugog hook-a i potrebna ti je stabilna referenca. Omotavanje svake funkcije dodaje overhead i buku u kodu.
Može li useRef čuvati bilo koji tip vrednosti?
Da. Iako je najvidljiviji slučaj upotrebe čuvanje referenci na DOM elemente, useRef može da čuva bilo koju mutatabilnu vrednost: ID-jeve tajmera, prethodne vrednosti state-a, brojače, WebSocket instance — sve što želiš da opstane između rendera bez pokretanja re-rendera.
Kako da znam da li moja komponenta zaista treba optimizaciju?
Koristi React DevTools Profiler da snimiš interakcije i potražiš komponente koje se renderuju češće nego što bi trebalo, ili čije vreme renderovanja je nesrazmerno visoko. Ne optimizuj po instinktu — optimizuj na osnovu podataka.
Serijal: React Masterclass
- 1
- 2
- 3
- 4Napredni React Hook-ovi: useMemo, useCallback i useRef (You are here)
- 5
- 6
- 7