Understanding useRef and When to Ditch useState
The useRef hook is a pillar of modern React development, especially when working with mobile interfaces in React Native. It's the essential tool for escaping the declarative world of React and dipping your toes into low-level component control.
1. What is useRef? (The Elevator Pitch)
useRef is a built-in React Hook that provides an object whose value persists across component re-renders. It serves two main purposes:
- Component Reference (The Analogy): To get a direct handle on a component instance (like a
TextInputorScrollView) so you can manually control action-oriented functions. - Component-Specific Memory: To hold any value (a counter, a timer ID, etc.) that needs to change without forcing the component to re-render. This memory is persistent between renders.
It returns a simple object: $\{ \text{current}: \text{value} \}$.
2. 🆚 The Web Analogy: useRef vs. document.getElementById
For web developers transitioning to React Native, the most helpful way to understand useRef is to think of it as your replacement for reaching into the Document Object Model (DOM).
| Feature | React Native with useRef
|
Web JavaScript with ID |
|---|---|---|
| Action | Programmatically Focus a TextInput. |
Programmatically Focus an <input>. |
| Mechanism | You assign the ref object to the ref prop: <TextInput **ref={inputRef}** />
|
You assign an ID to the element: <input **id="target"** />
|
| Access | inputRef.current.focus() |
document.getElementById("target").focus() |
The core similarity is that both methods allow you to manually control the underlying UI element, bypassing the standard declarative flow of data via props and state.
3. 🗺️ Real-World Example: Controlling a Map Component
This is where useRef truly shines. When working with complex third-party components like MapView (from react-native-maps), you often need to trigger animations or change the view without causing a costly re-render.
The Problem with Using State
If you tried to manage the map's location using state, it would look like this:
// DON'T DO THIS for animation/control!
const [region, setRegion] = useState({ /* ...coords... */ });
// To move the map:
setRegion({ new_coords }); // This causes a re-render!
The issue: Changing the map region via state often causes the entire MapView component to re-render from scratch. This is slow, can be visually jarring, and typically prevents the smooth, native-level camera animations that the map component is capable of.
The Solution with useRef
By using useRef, we gain access to the map's internal action-oriented functions, such as animateToRegion(), which uses the native platform's efficient animation logic.
import React, { useRef } from 'react';
import MapView from 'react-native-maps'; // Hypothetical import
const MapControlDemo = () => {
// 1. Declare the ref
const mapRef = useRef(null);
const flyToAuckland = () => {
const aucklandCoords = { latitude: -36.84, longitude: 174.76, latitudeDelta: 0.1, longitudeDelta: 0.1 };
if (mapRef.current) {
// Manually control the map instance and call its action-oriented function
mapRef.current.animateToRegion(aucklandCoords, 1000); // Smooth 1000ms animation
}
};
return (
<View style={styles.container}>
{/* 4. Attach the ref to the MapView component */}
<MapView ref={mapRef} style={styles.map} initialRegion={{ /* ... */ }} />
<Button title="Fly to Auckland" onPress={flyToAuckland} />
</View>
);
};
Using this approach:
- We don't call
setState, so the component doesn't re-render. - We manually control the native map instance directly: "Animate to this new location."
4. 💡 Other Common Uses of useRef
Beyond accessing component instances, useRef is your go-to for managing any data that needs to persist across renders without triggering a new render cycle.
| Use Case | Why useRef is Used |
|---|---|
| Timers & Intervals | To store the ID returned by setInterval or setTimeout so you can clear it later (in a useEffect cleanup function). |
| Previous Values | To store the component's previous prop or state value, allowing you to compare current and former values in a useEffect hook. |
| Component Counters | To count how many times a component has rendered. Using state would cause an infinite render loop; useRef does not. |
| Event Handlers | To access the latest version of a state variable inside an event handler without re-creating the function (advanced performance optimization). |
In short, if you need a static piece of Component-Specific Memory that doesn't interact with the visual rendering, useRef is your answer.
Top comments (0)