React Native Hooks in Depth
Hooks are functions that let you add React features — state, side effects, context — to your components. You already used useState in earlier topics. This topic covers the most important hooks you will reach for in every real app.
Why Hooks Exist
Before hooks, sharing logic between components was awkward and required complex patterns. Hooks package logic into reusable functions that any component can call. They make components shorter, cleaner, and easier to test.
useState — Recap
useState creates a value that the component remembers between renders. When the value changes, the component re-renders.
const [count, setCount] = useState(0); // count = current value // setCount = function to update it // 0 = initial value
useEffect — Running Code at the Right Time
useEffect runs a block of code in response to something changing — the component mounting, a state value changing, or the component unmounting. Use it to fetch data, set up subscriptions, or interact with device APIs.
import { useEffect, useState } from 'react';
function NewsScreen() {
const [articles, setArticles] = useState([]);
useEffect(() => {
// This runs when the component first appears on screen
fetchArticles();
}, []);
// ↑ Empty array = run once only (on mount)
async function fetchArticles() {
const response = await fetch('https://api.example.com/news');
const data = await response.json();
setArticles(data);
}
return <FlatList data={articles} ... />;
}
The Dependency Array
The second argument to useEffect controls when it runs.
useEffect(() => {
// runs after EVERY render
});
useEffect(() => {
// runs ONCE when component appears
}, []);
useEffect(() => {
// runs when `userId` changes
}, [userId]);
useEffect(() => {
// runs when `page` or `filter` changes
}, [page, filter]);
Cleanup Function
Some effects need to clean up after themselves — cancelling a timer, removing an event listener, or closing a connection. Return a function from useEffect to handle cleanup.
useEffect(() => {
const timer = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => {
clearInterval(timer); // stops the timer when component leaves screen
};
}, []);
Component Appears Component Disappears
│ │
▼ ▼
useEffect runs cleanup function runs
timer starts timer stops ✓
useRef — Persistent Values Without Re-renders
useRef holds a value that persists between renders but changing it does not trigger a re-render. Use it to reference a component directly or store values you need to track silently.
Focusing a TextInput Automatically
import { useRef, useEffect } from 'react';
import { TextInput } from 'react-native';
function SearchScreen() {
const inputRef = useRef(null);
useEffect(() => {
// Focus the input as soon as the screen appears
inputRef.current?.focus();
}, []);
return (
<TextInput
ref={inputRef}
placeholder="Search..."
/>
);
}
Tracking a Value Silently
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
// Updating renderCount.current does NOT cause a re-render
// Useful for debugging or tracking without visual side effects
});
useCallback — Stable Function References
useCallback returns a memorized version of a function. The function only changes if its dependencies change. Use it to prevent child components from re-rendering unnecessarily when a parent re-renders.
const handlePress = useCallback(() => {
navigation.navigate('Details', { id: itemId });
}, [itemId]);
// handlePress only recreates when itemId changes
useMemo — Expensive Calculations
useMemo caches the result of an expensive calculation. It only recalculates when its dependencies change.
const sortedList = useMemo(() => {
return items.sort((a, b) => a.price - b.price);
}, [items]);
// Sorting only runs again when items changes
// Not on every single render
Without useMemo: With useMemo: ──────────────────────────────────────────── Sort runs on EVERY render Sort runs only when items changes Render 1 → sort → result Render 1 → sort → cache result Render 2 → sort → result Render 2 → ← use cached result Render 3 → sort → result Render 3 → ← use cached result Render 4 → sort → result items changes → sort → cache again
Custom Hooks — Reusing Logic
You can combine built-in hooks into your own custom hook. A custom hook is just a function whose name starts with use. It extracts logic so multiple components share the same code without duplicating it.
// Custom hook that fetches data from any URL
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function load() {
try {
const res = await fetch(url);
const json = await res.json();
setData(json);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
load();
}, [url]);
return { data, loading, error };
}
// Use it in any component
function NewsScreen() {
const { data, loading, error } = useFetch('https://api.example.com/news');
if (loading) return <ActivityIndicator />;
if (error) return <Text>Something went wrong.</Text>;
return <FlatList data={data} ... />;
}
function ArticleScreen() {
const { data } = useFetch('https://api.example.com/articles');
// Same logic, zero code duplication
}
useFetch hook:
┌────────────────────────────────────────────┐
│ Input: URL string │
│ Internally uses: useState + useEffect │
│ Output: { data, loading, error } │
└────────────────────────────────────────────┘
↓ ↓ ↓
NewsScreen ArticleScreen ProfileScreen
all share the same fetching logic
Rules of Hooks
Follow these two rules every time you write hooks. Breaking them causes bugs that are hard to trace.
Rule 1: Only call hooks at the TOP LEVEL of a component.
Do NOT call hooks inside loops, conditions, or nested functions.
✗ WRONG:
if (isLoggedIn) {
const [name, setName] = useState(''); // NEVER inside an if
}
✓ CORRECT:
const [name, setName] = useState(''); // always at the top
Rule 2: Only call hooks inside React components or custom hooks.
Do NOT call hooks in regular JavaScript functions.
Summary
useEffect runs code at specific points in a component's life — on mount, on value change, and on unmount. useRef holds values without causing re-renders. useCallback and useMemo prevent unnecessary work. Custom hooks extract shared logic into reusable functions. Always call hooks at the top level of a component.
