What is useRef?
The useRef hook creates a mutable reference that persists across renders without causing re-renders when it changes. It's like a box that holds a value — you can put anything in it, change it whenever you want, and the component won't re-render.
Unlike useState, updating a ref doesn't trigger a re-render. This makes it perfect for values you need to keep but don't want to display directly — like timers, previous values, or DOM references.
Storing Timer IDs
A common use case is storing interval or timeout IDs so you can clear them later:
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, Button } from 'react-native';
function Stopwatch() {
const [time, setTime] = useState(0);
const [running, setRunning] = useState(false);
const intervalRef = useRef(null);
const start = () => {
setRunning(true);
intervalRef.current = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
};
const stop = () => {
setRunning(false);
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
useEffect(() => {
return () => clearInterval(intervalRef.current);
}, []);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 48 }}>{time}s</Text>
<Button title={running ? 'Stop' : 'Start'} onPress={running ? stop : start} />
</View>
);
}
export default Stopwatch;
We store the interval ID in intervalRef.current. When we need to clear the interval, we access it through the ref. The component doesn't re-render when we update the ref — it's just a container for the ID. This is cleaner than trying to store timer IDs in state.
Tracking Previous Values
Refs are useful for keeping track of the previous value of a prop or state. This is helpful for comparisons and animations.
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 24 }}>
Now: {count}, Before: {prevCount}
</Text>
<Button title="Increment" onPress={() => setCount(c => c + 1)} />
</View>
);
}
The usePrevious hook stores the current value in a ref after each render. When you read ref.current, you get the value from the previous render. This pattern is useful for animations, comparisons, and any logic that needs to know what the value was before.
Accessing Components
Refs can also reference component instances, letting you call methods on child components:
import React, { useRef } from 'react';
import { View, TextInput, Button } from 'react-native';
function SearchBar() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<View style={{ padding: 20 }}>
<TextInput
ref={inputRef}
placeholder="Search..."
style={{ borderWidth: 1, padding: 10 }}
/>
<Button title="Focus Input" onPress={focusInput} />
</View>
);
}
export default SearchBar;
The ref prop on TextInput gives you a reference to the underlying component. You can then call methods like focus(), clear(), or isFocused() on it. This is useful for programmatic control — focusing inputs, scrolling to elements, or triggering animations.