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.

Leave a Comment