useRef — to hook React, który tworzy mutowalny obiekt ref z właściwością .current, zachowywany między renderowaniami komponentu. W przeciwieństwie do useState, zmiana .current nie powoduje przerenderowania, co czyni useRef idealnym do przechowywania referencji DOM, timerów i wszelkich wartości, które powinny przetrwać rendery bez przerysowywania UI. Według React Documentation (2025), obiekt ref jest tworzony raz na czas życia komponentu i nie zmienia się między renderowaniami, co gwarantuje stabilność referencji.
Najważniejsze
useRef — to hook dodany w React 16.8, który zwraca mutowalny obiekt ref z pojedynczą właściwością .current. Wartość początkowa jest przypisywana do .current podczas montowania komponentu. Główna różnica w porównaniu ze zwykłą zmienną — obiekt ref przetrwa przerenderowania: przy ponownym renderowaniu React nie tworzy nowego refa, ale zwraca ten sam obiekt.
Wewnętrzna implementacja useRef wykorzystuje ten sam mechanizm co useState i useEffect — połączoną listę hooków na węźle fiber. Jednak w przeciwieństwie do useState, React nie śledzi zmian obiektu ref, nie kolejkuje aktualizacji i nie planuje przerenderowania. To sprawia, że useRef jest niezwykle lekkim hookiem, nie wpływającym na wydajność nawet przy częstych zmianach.
Według React Team — Hooks FAQ (2025), useRef to w zasadzie „pudełko”, które przechowuje wartość. Cukier składniowy: const ref = useRef(initialValue) jest równoważne const ref = { current: initialValue } z tą różnicą, że React gwarantuje, że obiekt ref będzie ten sam przy każdym renderowaniu. Żadnej magii — po prostu stabilna referencja zarządzana przez React.
import { useRef } from 'react';
function Component() {
const countRef = useRef(0);
// countRef.current = 0 początkowo
// countRef.current = 5 po mutacji
// Nie następuje przerenderowanie!
}
Najczęstszy scenariusz użycia useRef — uzyskanie bezpośredniego dostępu do elementu DOM. React przypisuje elementRef.current referencję do węzła DOM po zamontowaniu komponentu. Jest to konieczne do ustawiania fokusu na input, mierzenia rozmiarów elementu, integracji z bibliotekami animacji i bibliotekami, które nie używają renderowania React.
React automatycznie zarządza referencjami ref do DOM: podczas montowania przypisuje rzeczywisty węzeł DOM, podczas odmontowywania zeruje (ustawia null). Gwarantuje to, że w .current zawsze jest aktualna wartość i zapobiega wyciekom pamięci przez „zwiszące” referencje do usuniętych elementów. Nie wymaga ręcznego czyszczenia.
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// Skupienie inputa po zamontowaniu komponentu
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
Według React — Refs and DOM documentation (2025), nie używaj ref do operacji deklaratywnych — otwierania/zamykania modali, zarządzania widocznością. Do tych celów służą state i propsy. Refs są przeznaczone do operacji imperatywnych, których nie da się wyrazić deklaratywnie: fokus, zaznaczanie tekstu, integracja z bibliotekami zewnętrznymi.
Drugi co do ważności scenariusz — przechowywanie dowolnych wartości, które powinny być zachowane między renderowaniami, ale których zmiana nie powinna powodować przerenderowania. Typowe przykłady: identyfikatory timerów (setInterval/setTimeout), flagi do anulowania zapytań, poprzednie wartości propsów do porównania i wszelkie dane nie wpływające na wyjście UI.
Szczególnie przydatne jest to w useEffect: useRef przechowuje identyfikator timera, a cleanup go czyści. Jeśli przechowywać timerId w useState, każde wywołanie setTimerId powodowałoby zbędne przerenderowanie, które nie jest potrzebne ani dla logiki, ani dla UI. useRef rozwiązuje ten problem bez narzutu i bez zbędnych przerysowań.
function Timer() {
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
console.log('tyknięcie');
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
useEffect(() => stop, []);
return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}
Główny dylemat — wybór między useRef a useState — rozwiązuje jedno pytanie: „Czy potrzebne jest przerenderowanie przy zmianie wartości?”. Jeśli tak — useState. Jeśli nie — useRef. useState przechowuje stan wpływający na wyjście komponentu; useRef przechowuje dane potrzebne do wewnętrznej logiki, ale nie wpływające na UI.
W praktyce developers często używają useRef do przechowywania callbacków, aby uniknąć problemów z domknięciami. Na przykład, jeśli useEffect subskrybuje zdarzenie, a wewnątrz callbacka potrzebny jest aktualny state — przechowuj callback w useRef. Przy każdym renderowaniu aktualizuj ref.current nową funkcją, a efekt zawsze będzie wywoływać świeży callback bez ponownej subskrypcji.
| Kryterium | useRef | useState |
|---|---|---|
| Przerenderowanie | Nie powoduje przy zmianie | Powoduje przy każdym setState |
| Mutacja | Bezpośrednia: ref.current = value | Przez setter: setState(value) |
| Użycie w JSX | Nie używane (brak efektu) | Używane w wyjściu komponentu |
| Przykład | Timery, referencje DOM, poprzednie wartości | Dane formularza, stan UI, flagi |
Istnieje anty-wzorzec: używanie useRef do przechowywania danych potrzebnych w JSX, ale których zmiana nie powinna powodować przerenderowania. Prowadzi to do desynchronizacji — UI pokazuje stare dane, mimo że ref.current jest już nowy. Jeśli wartość jest wyświetlana w UI — używaj useState. Jeśli używana tylko wewnątrz logiki — useRef.
Połączenie useRef + useEffect — standardowy wzorzec do śledzenia poprzednich wartości propsów. Przechowuj poprzednią wartość w ref, porównuj z bieżącą w useEffect i podejmuj decyzję na podstawie różnicy. Jest to szczególnie przydatne przy animacjach, gdy trzeba wiedzieć, jaka wartość była przed zmianą.
function PriceDisplay({ price }) {
const prevPriceRef = useRef(price);
useEffect(() => {
const prevPrice = prevPriceRef.current;
if (price > prevPrice) {
animateUp();
} else if (price < prevPrice) {
animateDown();
}
prevPriceRef.current = price;
}, [price]);
return <span>${price}</span>;
}
Według React Documentation — Hooks FAQ (2025), ten wzorzec nazywa się „previous value pattern”. Działa, ponieważ useRef zachowuje wartość między renderowaniami, a useEffect wykonuje się po zatwierdzeniu zmian. Najpierw aktualizowany jest DOM z nowym price, następnie useEffect porównuje z poprzednim (które wciąż jest w ref) i aktualizuje ref.current do bieżącej wartości.
Najczęstszy błąd — czytanie ref.current w fazie renderowania do obliczania JSX. Ponieważ zmiana ref.current nie powoduje przerenderowania, komponent może używać nieaktualnej wartości. Jeśli wartość .current uczestniczy w wyjściu UI — używaj useState. Jeśli potrzebujesz synchronizować ref i state, używaj useEffect do aktualizacji state z ref.
// ❌ Nie czytaj ref.current podczas renderowania do wyświetlania
function BadComponent() {
const valRef = useRef(0);
return <p>{valRef.current}</p>; // NIE zaktualizuje się po mutacji
}
// ✅ Użyj state do wyświetlania, ref do logiki
function GoodComponent() {
const [val, setVal] = useState(0);
const valRef = useRef(0);
return <p>{val}</p>;
}
Innym częstym błędem jest używanie useRef jako jedynego sposobu przechowywania stanu w komponencie, gdy dane jednak wpływają na UI. Developerzy wybierają useRef, aby uniknąć „zbędnych” przerenderowań, ale w efekcie otrzymują UI, które się nie aktualizuje. Prawidłowe podejście: używaj useState do danych UI, a useRef tylko do wartości pomocniczych nieuczestniczących w renderowaniu.
Często zadawane pytania
Tak, to powszechny wzorzec — previous state pattern. Stwórz ref i aktualizuj go w useEffect przy każdej zmianie śledzonej wartości. W momencie między renderowaniem a efektem ref.current zawiera poprzednią wartość, którą można porównać z bieżącą. Nie wymaga to dodatkowych przerenderowań i działa z dowolnymi typami danych.
React przypisuje ref.current wartość węzła DOM dopiero po tym, jak element jest wyrenderowany i dodany do rzeczywistego DOM. Podczas pierwszego renderowania komponent nie jest jeszcze zamontowany, dlatego ref.current jest równy wartości początkowej (null). Dostęp do DOM przez ref.current jest możliwy tylko w useEffect lub w handlerach zdarzeń, które są wywoływane po zamontowaniu.
createRef tworzy nowy obiekt ref przy każdym renderowaniu — należy go używać tylko w komponentach klasowych. useRef tworzy ref raz i zwraca ten sam obiekt przy wszystkich kolejnych renderowaniach. W komponentach funkcyjnych używaj wyłącznie useRef; createRef spowoduje utratę wartości przy przerenderowaniu, ponieważ obiekt zostanie utworzony od nowa.
Tak, obiekt ref można przekazywać jako zwykły prop: <Child inputRef={inputRef} />. Komponent potomny używa go przez inputRef.current. Jednak do przekazania ref bezpośrednio do elementu DOM komponentu potomnego używaj forwardRef — HOC, który pozwala przerzucić ref przez props. Bez forwardRef ref nie zostanie automatycznie powiązany z elementem DOM wewnątrz komponentu potomnego.
Przypięcie ref do elementu, następnie w useEffect czytaj ref.current.getBoundingClientRect() lub ref.current.offsetWidth / offsetHeight. Do reaktywnego śledzenia zmian rozmiaru używaj ResizeObserver wewnątrz useEffect: utwórz observer, subskrybuj zmiany i aktualizuj state z nowymi rozmiarami. Nie zapomnij wyłączyć observera w cleanup.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również