SalakCode SalakCode
React Internals

Context API

Share data across the component tree

Intermediate
react state props composition

Definition

Context provides a way to pass data through the component tree without having to pass props down manually at every level. It’s designed to share data that can be considered “global” for a tree of React components, such as current authenticated user, theme, or preferred language.

Basic Usage

Creating Context

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
      <MainContent />
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  
  return (
    <button
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
      className={theme}
    >
      Toggle Theme
    </button>
  );
}

Context with Provider Pattern

Auth Context

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  const login = async (email, password) => {
    const user = await api.login(email, password);
    setUser(user);
  };
  
  const logout = () => {
    api.logout();
    setUser(null);
  };
  
  const value = {
    user,
    login,
    logout,
    loading,
    isAuthenticated: !!user
  };
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

// Usage
function UserProfile() {
  const { user, logout } = useAuth();
  
  return (
    <div>
      <p>Welcome, {user.name}</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

Performance Optimization

Split Contexts

// Instead of one big context, split by concern
const ThemeContext = createContext(null);
const UserContext = createContext(null);
const LocaleContext = createContext(null);

// Components only subscribe to what they need
function ThemedComponent() {
  const theme = useContext(ThemeContext); // Won't re-render on user changes
  return <div className={theme}>...</div>;
}

Memoization

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  // Memoize the context value
  const value = useMemo(() => ({
    theme,
    setTheme
  }), [theme]);
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

Common Patterns

Compound Components

const TabsContext = createContext(null);

function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      {children}
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ id, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  
  return (
    <button
      className={activeTab === id ? 'active' : ''}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  );
}

function TabPanel({ id, children }) {
  const { activeTab } = useContext(TabsContext);
  
  if (activeTab !== id) return null;
  return <div className="tab-panel">{children}</div>;
}

// Usage
<Tabs defaultTab="1">
  <TabList>
    <Tab id="1">Tab 1</Tab>
    <Tab id="2">Tab 2</Tab>
  </TabList>
  <TabPanel id="1">Content 1</TabPanel>
  <TabPanel id="2">Content 2</TabPanel>
</Tabs>

When Not to Use Context

// Prop drilling is fine for simple cases
function App() {
  const user = { name: 'John' };
  return <Layout user={user} />;
}

function Layout({ user }) {
  return <Header user={user} />;
}

function Header({ user }) {
  return <UserMenu user={user} />;
}

// Use Context when:
// 1. Many components need the data
// 2. Intermediate components don't use the data
// 3. Data changes frequently and affects many components
Key Takeaway

Context API eliminates prop drilling for truly global data. Create custom hooks with useContext for clean consumption, split contexts by concern to minimize re-renders, and always memoize context values. Remember: Context is not a state management solution - it’s a dependency injection mechanism.

Resources

Related Topics