React Native Context API State Management

As your app grows, passing data through props across many layers becomes painful. Context API solves this by making data available to any component in the tree without threading props manually through every level.

The Prop Drilling Problem

Without Context — "Prop Drilling":

App (holds user data)
 └── Layout (passes user down)
      └── Header (passes user down)
           └── AvatarButton (finally uses user!)

user must be passed at every level even if Layout and Header don't use it.

App
 │  user={user}
 ▼
Layout
 │  user={user}   ← doesn't need it, just passes it on
 ▼
Header
 │  user={user}   ← doesn't need it, just passes it on
 ▼
AvatarButton     ← finally uses user.avatar
With Context — Any component reads it directly:

App (provides user via Context)
 └── Layout
      └── Header
           └── AvatarButton ──reads──► Context (gets user directly)

Three Steps to Use Context

Step 1: CREATE the context
Step 2: PROVIDE the context (wrap components with a Provider)
Step 3: CONSUME the context (read it in any child component)

Step 1 — Create the Context

// context/AuthContext.js
import { createContext, useContext, useState } from 'react';

const AuthContext = createContext(null);
// null is the default value — used when no Provider is above in the tree

Step 2 — Create the Provider

The Provider wraps parts of your app and makes the context value available to all descendants.

// context/AuthContext.js (continued)

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(null);

  function login(userData, authToken) {
    setUser(userData);
    setToken(authToken);
  }

  function logout() {
    setUser(null);
    setToken(null);
  }

  return (
    <AuthContext.Provider value={{ user, token, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}

Step 3 — Wrap Your App with the Provider

// App.js
import { AuthProvider } from './context/AuthContext';

export default function App() {
  return (
    <AuthProvider>
      <NavigationContainer>
        {/* All screens inside here can access auth context */}
      </NavigationContainer>
    </AuthProvider>
  );
}

Step 4 — Consume in Any Component

// screens/ProfileScreen.js
import { useAuth } from '../context/AuthContext';

export default function ProfileScreen() {
  const { user, logout } = useAuth();

  return (
    <View style={{ padding: 20 }}>
      <Text style={{ fontSize: 20 }}>Welcome, {user?.name}</Text>
      <Text style={{ color: '#666' }}>{user?.email}</Text>
      <TouchableOpacity onPress={logout}>
        <Text>Logout</Text>
      </TouchableOpacity>
    </View>
  );
}

// screens/HeaderAvatar.js — deeply nested, still works
import { useAuth } from '../context/AuthContext';

function HeaderAvatar() {
  const { user } = useAuth();   // reads directly — no prop needed
  return (
    <Image
      source={{ uri: user?.avatar }}
      style={{ width: 36, height: 36, borderRadius: 18 }}
    />
  );
}

Building a Theme Context

A theme context lets every component know whether dark mode is on, and provides the right colors automatically.

// context/ThemeContext.js
import { createContext, useContext, useState } from 'react';

const light = {
  background: '#ffffff',
  text: '#111111',
  card: '#f5f5f5',
  primary: '#007AFF',
};

const dark = {
  background: '#000000',
  text: '#ffffff',
  card: '#1c1c1e',
  primary: '#0A84FF',
};

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [isDark, setIsDark] = useState(false);
  const colors = isDark ? dark : light;

  return (
    <ThemeContext.Provider value={{ colors, isDark, setIsDark }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}
// Any screen uses colors without knowing isDark or the full theme object
function HomeScreen() {
  const { colors } = useTheme();

  return (
    <View style={{ flex: 1, backgroundColor: colors.background }}>
      <Text style={{ color: colors.text }}>Home Screen</Text>
    </View>
  );
}
Theme switch diagram:
isDark = false               isDark = true
┌────────────────────┐       ┌────────────────────┐
│ bg: white          │       │ bg: black          │
│ text: #111         │  ───► │ text: white        │
│ card: #f5f5f5      │       │ card: #1c1c1e      │
└────────────────────┘       └────────────────────┘
        All screens update instantly

Multiple Contexts Together

export default function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <CartProvider>
          <NavigationContainer>
            {/* All three contexts available everywhere */}
          </NavigationContainer>
        </CartProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

When Context Re-renders

When the context value changes, every component that consumes it re-renders. Keep context values focused. Split a large context into smaller ones so a color preference change does not force the user list to re-render.

Too broad (causes unnecessary re-renders):
<AppContext.Provider value={{ user, theme, cart, posts, settings }}>

Better (each context updates independently):
<AuthContext.Provider value={{ user }}>
  <ThemeContext.Provider value={{ theme }}>
    <CartContext.Provider value={{ cart }}>

Context vs Redux

Context API                        Redux
────────────────────────────────────────────────────────────
Built into React — no install      Separate library (redux toolkit)
Great for simple global state      Better for complex state logic
Easy to learn                      More setup, more powerful
Good for: theme, auth, language    Good for: large apps, many actions

Start with Context API. Add Redux or Zustand later only when your state management becomes genuinely complex.

Summary

Context API eliminates prop drilling by making data available to any component in the tree. Create a context file with a Provider component and a custom hook. Wrap your app in the Provider. Call the custom hook in any component that needs the data. Keep each context focused on one concern — auth, theme, or cart — to avoid unnecessary re-renders.

Leave a Comment