Labs ICT
Pro Login

Context API

Sharing state across your component tree.

The Problem with Prop Drilling

Imagine you have data that needs to be shared across many components — like the current user's information or a theme setting. Without Context, you'd pass this data through props from component to component, even if intermediate components don't need it. This is called prop drilling, and it makes your code messy and hard to maintain.

Context solves this by letting you share data directly between components without passing it through every level of the tree. It's like creating a global data channel that any component can tap into.

Creating Context

Here's how to create and use a Context to share theme data across your app:

import React, { createContext, useContext, useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

// Create the context
const ThemeContext = createContext();

// Provider component that wraps your app
function ThemeProvider({ children }) {
  const [isDark, setIsDark] = useState(false);

  const toggleTheme = () => setIsDark(prev => !prev);

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

// Hook to use the context
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

// Component that uses the theme
function Header() {
  const { isDark } = useTheme();

  return (
    <View style={{ backgroundColor: isDark ? '#333' : '#fff', padding: 20 }}>
      <Text style={{ color: isDark ? '#fff' : '#333', fontSize: 24 }}>
        Header
      </Text>
    </View>
  );
}

// Another component using the same theme
function ToggleButton() {
  const { toggleTheme } = useTheme();

  return <Button title="Toggle Theme" onPress={toggleTheme} />;
}

// App with the provider
function App() {
  return (
    <ThemeProvider>
      <View style={{ flex: 1 }}>
        <Header />
        <ToggleButton />
      </View>
    </ThemeProvider>
  );
}

export default App;

The pattern is: create context, create a Provider that holds the state, wrap your app with the Provider, and use a custom hook to access the data. Any component anywhere in the tree can call useTheme() to get the current theme and toggle function.

User Authentication Example

A common use case is sharing authentication state across the app:

const AuthContext = createContext();

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

  const login = async (email, password) => {
    const userData = await loginUser(email, password);
    setUser(userData);
  };

  const logout = () => setUser(null);

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

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

// Any component can access auth
function ProfileScreen() {
  const { user, logout } = useAuth();

  if (!user) return <Text>Please log in</Text>;

  return (
    <View>
      <Text>Welcome, {user.name}!</Text>
      <Button title="Logout" onPress={logout} />
    </View>
  );
}

With Context, any component can access the user's login state without prop drilling. The login and logout functions are also available everywhere, so you can put login forms and logout buttons anywhere in your app without passing callbacks through multiple layers.

When to Use Context

Context is great for global data that many components need — themes, authentication, language settings, or user preferences. It's not meant for all state management. For frequently changing data that affects only a few components, local state or prop drilling might be simpler.

A good rule of thumb: if you're passing props through more than two levels, consider Context. If only one or two components need the data, keep it local. Context is powerful but adds complexity — use it when the benefits outweigh the overhead.