Popular Searches
Popular Course Categories
Popular Courses

State Management Using React Context API

State Management Using React Context API

State Management in React Native

State Management Using React Context API in React Native

Course: React Native Training

Topic: State Management Using React Context API

Course Link: React Native Training at JustAcademy

Demo Registration: Register for a Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to State Management

State management is an essential part of React Native development. Applications need to store and update information such as user details, authentication status, theme preferences, shopping cart items, selected language, and notification settings.

In a small application, a component can manage its own state using useState or useReducer. However, when multiple components need access to the same information, passing props through many intermediate components can make the application difficult to maintain.

The React Context API provides a way to share values across a component tree without manually passing props through every intermediate component. When combined with state Hooks, Context can help manage shared application state.

1.1 Example of Shared State

Imagine a React Native shopping application containing the following screens and components:

  • Home Screen
  • Product List
  • Product Details
  • Shopping Cart
  • User Profile
  • Navigation Header

If the shopping cart count must appear in the navigation header and the cart screen, both components need access to the same cart state. Context can provide that shared state to components inside its provider.

1.2 Learning Objectives

  • Understand state management in React Native.
  • Learn the purpose of the React Context API.
  • Understand prop drilling and how Context can reduce it.
  • Create and configure a context using createContext.
  • Share values through a context provider.
  • Read shared values using useContext.
  • Combine Context with useState and useReducer.
  • Create reusable custom Hooks for accessing shared state.
  • Implement theme, authentication, and shopping cart examples.
  • Understand performance considerations, limitations, and best practices.

2. What Is the React Context API?

The React Context API is a React feature that allows components to receive information from a parent component without requiring every intermediate component to pass that information through props.

Context itself does not automatically manage changing data. Instead, it provides a mechanism for making a value available to descendant components. To manage changing values, Context is commonly combined with state Hooks such as useState or useReducer.

2.1 Common Uses of Context

Use Case Shared Information Example
Authentication Current user and sign-out action Profile and account screens
Theme management Light or dark theme Buttons, screens, and headers
Shopping cart Products and quantities Cart screen and cart badge
Language settings Selected language Labels across the application
User preferences Display or accessibility options Settings and profile screens
Application configuration Feature flags or configuration values Multiple components within a provider

2.2 Context API Components

Three concepts are especially important when working with Context:

  1. createContext: Creates a context object that components can provide and consume.
  2. Provider: Supplies a value to descendant components. In React 19, the context object itself can be rendered as the provider; in earlier React versions, use Context.Provider.
  3. useContext: Reads the value from the nearest matching provider above the component.

3. Understanding Prop Drilling

3.1 What Is Prop Drilling?

Prop drilling occurs when a value is passed through multiple intermediate components even though those components do not need to use the value themselves.

For example, a parent may pass a user's name to a navigation component, then to a layout component, and finally to a profile button.

function App() {
  const user = { name: 'Aarav' };

  return <Dashboard user={user} />;
}

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

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

function ProfileButton({ user }) {
  return <Text>Welcome, {user.name}</Text>;
}

The Dashboard and Header components only pass the user object along. In a large application, this pattern can make component interfaces harder to maintain.

3.2 Problems with Prop Drilling

  • Intermediate components receive props they do not actually use.
  • Adding or removing shared data may require updating several component interfaces.
  • Component hierarchies can become more difficult to understand.
  • Shared values may need to be passed through many layers of navigation and layout components.

3.3 How Context Helps

Context lets a parent provide the user object once. Components below that provider can read it directly using useContext, without receiving it as a prop from every intermediate component.

Context is not a replacement for all props. Props remain useful for explicit, component-specific data. Context is most helpful when information needs to be shared across multiple descendants.

4. Creating a Context with createContext

4.1 Basic Syntax

import { createContext } from 'react';

const MyContext = createContext(defaultValue);

The createContext function creates a context object. It is normally called outside components so the same context object can be imported wherever it is needed.

4.2 Understanding the Default Value

The default value is used when a component reads a context and there is no matching provider above it in the component tree. This default is a fallback; it is not automatically updated when application state changes.

import { createContext } from 'react';

export const ThemeContext = createContext('light');

In this example, a component that reads ThemeContext without a matching provider receives 'light'.

4.3 Creating Context for a User

import { createContext } from 'react';

export const UserContext = createContext(null);

Here, null represents the fallback value when there is no matching provider. Components can use this context to access the current user when a provider supplies one.

5. Providing Values with a Context Provider

Creating a context does not automatically make application data available. A component must provide a value to the context so descendant components can read it.

5.1 Provider Syntax

For projects using React 18 and earlier, use the .Provider syntax:

<ThemeContext.Provider value="dark">
  <HomeScreen />
</ThemeContext.Provider>

React 19 also supports using the context object directly as a provider:

<ThemeContext value="dark">
  <HomeScreen />
</ThemeContext>

Use the syntax supported by the React version in your project. The following examples use .Provider for compatibility with React Native projects using older React versions.

5.2 Provider Responsibilities

  • Supplies the current context value.
  • Makes that value available to descendant components.
  • Can supply state variables, objects, functions, or reducer dispatch functions.
  • Can update the value when its state changes.
  • Defines the part of the component tree that can access the provided value.

6. Reading Context with useContext

6.1 Basic Syntax

import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

const theme = useContext(ThemeContext);

The useContext Hook reads the value from the closest matching provider above the component that calls it.

6.2 Example Component

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

const UserContext = createContext(null);

function WelcomeMessage() {
  const user = useContext(UserContext);

  return (
    <View>
      <Text>
        {user ? `Welcome, ${user.name}` : 'Welcome, Guest'}
      </Text>
    </View>
  );
}

export default function App() {
  const user = { name: 'Aarav' };

  return (
    <UserContext.Provider value={user}>
      <WelcomeMessage />
    </UserContext.Provider>
  );
}

6.3 Explanation

  • UserContext defines the shared context.
  • UserContext.Provider supplies the user object.
  • WelcomeMessage reads the context with useContext.
  • The component displays a personalized greeting.
  • If the provider value changes, components reading that context receive the updated value and can render again.

7. Building a Theme Manager with Context API

A theme manager is a practical example because multiple components may need to use the same theme. A theme can contain colors, background values, text colors, and other visual preferences.

7.1 Create ThemeContext.js

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

const ThemeContext = createContext(null);

const themes = {
  light: {
    name: 'light',
    background: '#FFFFFF',
    text: '#222222',
    button: '#E5E7EB'
  },
  dark: {
    name: 'dark',
    background: '#222222',
    text: '#FFFFFF',
    button: '#444444'
  }
};

export function ThemeProvider({ children }) {
  const [themeName, setThemeName] = useState('light');

  function toggleTheme() {
    setThemeName(current =>
      current === 'light' ? 'dark' : 'light'
    );
  }

  const value = useMemo(() => ({
    theme: themes[themeName],
    themeName,
    toggleTheme
  }), [themeName]);

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

export function useTheme() {
  const context = useContext(ThemeContext);

  if (!context) {
    throw new Error('useTheme must be used inside ThemeProvider');
  }

  return context;
}

7.2 Create a Theme-Aware Screen

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

import { useTheme } from './ThemeContext';

export default function HomeScreen() {
  const { theme, themeName, toggleTheme } = useTheme();

  return (
    <View
      style={[
        styles.container,
        { backgroundColor: theme.background }
      ]}
    >
      <Text style={[styles.title, { color: theme.text }]}>
        Current Theme: {themeName}
      </Text>

      <Button
        title="Toggle Theme"
        onPress={toggleTheme}
        color={theme.button}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20
  },
  title: {
    fontSize: 22,
    marginBottom: 20
  }
});

7.3 Wrap the Application

import React from 'react';
import { ThemeProvider } from './ThemeContext';
import HomeScreen from './HomeScreen';

export default function App() {
  return (
    <ThemeProvider>
      <HomeScreen />
    </ThemeProvider>
  );
}

7.4 How It Works

  1. The ThemeProvider stores the selected theme in state.
  2. The provider supplies the current theme and a toggle function.
  3. HomeScreen reads the value through the custom useTheme Hook.
  4. When the user presses the button, the theme name changes.
  5. The provider value updates and consuming components can render with the new theme.

The example uses useMemo to keep the provided object stable when the theme name has not changed. It is a performance optimization, not a requirement for Context to work.

8. Combining Context API with useState

Context shares values, while useState stores changing data. Combining them is a straightforward way to build a shared state provider.

8.1 Create a Counter Context

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

const CounterContext = createContext(null);

export function CounterProvider({ children }) {
  const [count, setCount] = useState(0);

  function increment() {
    setCount(current => current + 1);
  }

  function decrement() {
    setCount(current => current - 1);
  }

  function reset() {
    setCount(0);
  }

  return (
    <CounterContext.Provider
      value={{ count, increment, decrement, reset }}
    >
      {children}
    </CounterContext.Provider>
  );
}

export function useCounter() {
  const context = useContext(CounterContext);

  if (!context) {
    throw new Error('useCounter must be used inside CounterProvider');
  }

  return context;
}

8.2 Use the Counter Context

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useCounter } from './CounterContext';

export default function CounterScreen() {
  const { count, increment, decrement, reset } = useCounter();

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Increment" onPress={increment} />
      <Button title="Decrement" onPress={decrement} />
      <Button title="Reset" onPress={reset} />
    </View>
  );
}

8.3 Advantages of This Pattern

  • Multiple descendant components can use the same counter.
  • The state update logic is defined in one provider.
  • Components do not need to receive the counter through intermediate props.
  • A custom Hook provides a consistent interface for accessing the shared state.

9. Combining Context API with useReducer

When shared state contains many related updates, combining Context with useReducer can keep the update rules organized. The provider supplies the current state and the dispatch function, while descendant components dispatch actions.

9.1 Create a CartContext.js File

import React, {
  createContext,
  useContext,
  useMemo,
  useReducer
} from 'react';

const CartContext = createContext(null);

const initialState = {
  items: []
};

function cartReducer(state, action) {
  switch (action.type) {
    case 'addItem': {
      const product = action.payload;
      const existingItem = state.items.find(
        item => item.id === product.id
      );

      if (existingItem) {
        return {
          ...state,
          items: state.items.map(item =>
            item.id === product.id
              ? { ...item, quantity: item.quantity + 1 }
              : item
          )
        };
      }

      return {
        ...state,
        items: [
          ...state.items,
          { ...product, quantity: 1 }
        ]
      };
    }

    case 'removeItem':
      return {
        ...state,
        items: state.items.filter(
          item => item.id !== action.payload
        )
      };

    case 'clearCart':
      return {
        ...state,
        items: []
      };

    default:
      throw new Error(`Unknown cart action: ${action.type}`);
  }
}

export function CartProvider({ children }) {
  const [state, dispatch] = useReducer(cartReducer, initialState);

  const totalItems = state.items.reduce(
    (total, item) => total + item.quantity,
    0
  );

  const totalPrice = state.items.reduce(
    (total, item) => total + item.price * item.quantity,
    0
  );

  const value = useMemo(() => ({
    items: state.items,
    totalItems,
    totalPrice,
    dispatch
  }), [state.items, totalItems, totalPrice]);

  return (
    <CartContext.Provider value={value}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  const context = useContext(CartContext);

  if (!context) {
    throw new Error('useCart must be used inside CartProvider');
  }

  return context;
}

9.2 Display Cart Information

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useCart } from './CartContext';

export default function CartSummary() {
  const { items, totalItems, totalPrice, dispatch } = useCart();

  return (
    <View>
      <Text>Items in cart: {totalItems}</Text>
      <Text>Total: ₹{totalPrice.toFixed(2)}</Text>

      {items.map(item => (
        <View key={item.id}>
          <Text>{item.name} × {item.quantity}</Text>
          <Button
            title="Remove"
            onPress={() =>
              dispatch({
                type: 'removeItem',
                payload: item.id
              })
            }
          />
        </View>
      ))}

      <Button
        title="Clear Cart"
        onPress={() => dispatch({ type: 'clearCart' })}
      />
    </View>
  );
}

9.3 Add a Product to the Cart

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useCart } from './CartContext';

export default function ProductCard({ product }) {
  const { dispatch } = useCart();

  function addToCart() {
    dispatch({
      type: 'addItem',
      payload: product
    });
  }

  return (
    <View>
      <Text>{product.name}</Text>
      <Text>₹{product.price}</Text>
      <Button title="Add to Cart" onPress={addToCart} />
    </View>
  );
}

The product object should contain an identifier, name, and valid numeric price. For a real shopping application, prices and inventory must be verified by the backend before checkout; client-side state is not a trusted source for payment calculations.

9.4 Wrap the Application with CartProvider

import React from 'react';
import { CartProvider } from './CartContext';
import ProductListScreen from './ProductListScreen';
import CartSummary from './CartSummary';

export default function App() {
  return (
    <CartProvider>
      <ProductListScreen />
      <CartSummary />
    </CartProvider>
  );
}

In an application using React Navigation, the provider can usually wrap the navigation container so that screens inside the navigation tree can access the cart context.

10. Implementing Authentication with Context API

Authentication is another common use case for Context because many components may need to know whether a user is signed in.

An authentication context might provide:

  • The current user.
  • A loading indicator while restoring a session.
  • A sign-in function.
  • A sign-out function.
  • An authentication status flag.

10.1 Create AuthContext.js

import React, {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useState
} from 'react';

const AuthContext = createContext(null);

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

  const signIn = useCallback(async credentials => {
    setIsLoading(true);

    try {
      // Replace this with your real authentication API.
      const authenticatedUser = await authenticateWithServer(credentials);
      setUser(authenticatedUser);
    } finally {
      setIsLoading(false);
    }
  }, []);

  const signOut = useCallback(async () => {
    // Invalidate the server session and clear secure credentials
    // according to your authentication provider's requirements.
    setUser(null);
  }, []);

  const value = useMemo(() => ({
    user,
    isLoading,
    isAuthenticated: Boolean(user),
    signIn,
    signOut
  }), [user, isLoading, signIn, signOut]);

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);

  if (!context) {
    throw new Error('useAuth must be used inside AuthProvider');
  }

  return context;
}

async function authenticateWithServer(credentials) {
  // Implement this function using your authentication service.
  // Do not hardcode successful authentication in production.
  throw new Error('Connect authenticateWithServer to your auth API');
}

Important: The authentication function above is a placeholder, not a working login implementation. Connect it to a real authentication provider, handle session restoration and token storage securely, and validate sessions with the server.

10.2 Display Authentication State

import React from 'react';
import { View, Text, Button, ActivityIndicator } from 'react-native';
import { useAuth } from './AuthContext';

export default function AccountScreen() {
  const { user, isLoading, isAuthenticated, signOut } = useAuth();

  if (isLoading) {
    return <ActivityIndicator size="large" />;
  }

  if (!isAuthenticated) {
    return <Text>Please sign in to view your account.</Text>;
  }

  return (
    <View>
      <Text>Welcome, {user.name}</Text>
      <Button title="Sign Out" onPress={signOut} />
    </View>
  );
}

10.3 Authentication and Navigation

Context can expose authentication state to a navigation system, but it does not automatically protect routes. Combine the authentication context with conditional navigation so that guest users and authenticated users see the appropriate screen groups.

Protected API endpoints must also validate credentials and permissions on the server. Hiding a screen in React Native is not sufficient security by itself.

11. Using Multiple Contexts

A React Native application may need several independent shared values. Instead of putting everything into one large context, create separate contexts according to their responsibilities.

For example:

  • AuthContext for authentication information.
  • ThemeContext for colors and appearance.
  • CartContext for shopping cart state.
  • LanguageContext for the selected language.

11.1 Nesting Providers

export default function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <CartProvider>
          <AppNavigation />
        </CartProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

Descendant components can access all the contexts provided above them. Provider order matters when one provider consumes another context, so place dependencies above the providers that use them.

11.2 Benefits of Separate Contexts

  • Each context has a clear responsibility.
  • Updates are easier to understand and maintain.
  • Components can subscribe only to the context values they need.
  • Testing can provide only the contexts required by a component.
  • Unrelated application concerns remain separated.

12. Context API Versus Props and Other State Solutions

Approach Best Use Advantages Limitations
Props Parent-to-child data flow Explicit and easy to trace Can require passing data through many layers
useState Local component state Simple and direct State is local unless explicitly shared
useReducer Complex state transitions Centralizes update rules Does not share state with distant components by itself
Context API Shared values across a component tree Reduces prop drilling Consumers update when their context value changes
Context with useReducer Shared state with multiple actions Combines shared access and structured updates Requires careful context and reducer design
Dedicated state library More complex global state requirements May provide selectors, debugging, middleware, or additional patterns Introduces another dependency and concepts

13. Context API Performance Considerations

Context is convenient, but its values should be designed carefully. When the value provided by a context changes, components that read that context are updated to receive the new value.

13.1 Avoid Unnecessary Provider Value Changes

Creating a new object or function on every provider render can cause context consumers to update even when the underlying data has not meaningfully changed.

For example:

<MyContext.Provider value={{ user, signOut }}>
  {children}
</MyContext.Provider>

The object expression creates a new object on each render. For larger applications, useMemo and useCallback can sometimes reduce unnecessary context value changes.

13.2 Memoize the Context Value When Appropriate

const signOut = useCallback(() => {
  setUser(null);
}, []);

const value = useMemo(() => ({
  user,
  signOut
}), [user, signOut]);

return (
  <AuthContext.Provider value={value}>
    {children}
  </AuthContext.Provider>
);

Memoization is a performance optimization, not a requirement. Measure or identify a real performance need before adding unnecessary complexity.

13.3 Split Frequently Changing Contexts

If unrelated values change at very different rates, consider separating them into different contexts. For example, a frequently changing animation value may not belong in the same context as stable application configuration.

13.4 Context Does Not Automatically Provide Fine-Grained Selectors

Components reading a context are updated when that context's provided value changes. Context does not automatically subscribe a component only to one field of an object. If an application requires extensive fine-grained subscriptions or complex global-state tooling, evaluate whether a dedicated state-management solution is more appropriate.

14. Common Mistakes and Troubleshooting

Problem Possible Cause Solution
Context value is null or undefined The component is outside the provider or the provider value is missing. Check the component hierarchy and ensure the correct value prop is supplied.
Context updates are not reflected The provided value does not change or state is mutated directly. Update state immutably and pass the current state as the provider value.
Cannot access context in a component The component calls useContext before any matching provider above it. Move the provider higher in the tree.
Unnecessary component updates The provider creates new objects or functions on each render. Consider useMemo and useCallback where useful.
Wrong context value is returned Another provider closer to the consumer overrides the outer value. Check nested providers and their values.
Custom Hook throws an error The Hook is used outside its provider. Wrap the relevant component with the matching provider.
Context is too large Unrelated application state is grouped together. Split state into contexts based on responsibility.
Private screens remain accessible Context state is used without appropriate navigation or backend checks. Implement conditional navigation and enforce server-side authorization.
Changes disappear after restarting the app Context state exists only in memory. Add appropriate persistence and restoration logic when needed.

15. Persisting Context State

Context state usually exists in memory while the application is running. It does not automatically save information to permanent storage. When the application restarts, its providers are initialized again unless state restoration is implemented.

15.1 When Persistence Is Useful

  • Saving user preferences.
  • Restoring a shopping cart between sessions.
  • Remembering the selected theme.
  • Restoring an authenticated session using a secure authentication strategy.

15.2 Storage Choices

Storage Option Typical Use Consideration
In-memory Context state Temporary shared state Usually resets when the application process restarts.
AsyncStorage Non-sensitive persistent preferences and cached values Not designed for secrets or tokens that require secure storage.
Secure storage Credentials or tokens when supported by the authentication design Use a platform-appropriate secure storage library and follow provider guidance.
Backend database Shared account data and authoritative application records Requires authentication, authorization, and network handling.

Context can hold the current in-memory representation of persisted data. Storage and restoration logic should be handled separately, with loading and error states where appropriate.

16. Best Practices for Context API State Management

  • Create context objects outside component functions.
  • Use meaningful context names such as AuthContext, ThemeContext, and CartContext.
  • Keep each context focused on a clear responsibility.
  • Use a provider component to own and expose changing state.
  • Create custom Hooks to simplify context consumption and validate provider usage.
  • Combine Context with useState for simple shared state.
  • Combine Context with useReducer for shared state with multiple related actions.
  • Use immutable updates for objects and arrays.
  • Use memoization only when it provides a practical performance benefit.
  • Avoid putting every piece of local component state into global Context.
  • Use props for simple, explicit parent-to-child data flow.
  • Separate state that changes frequently from unrelated, stable context values when useful.
  • Handle loading, error, and empty states in asynchronous workflows.
  • Use secure persistence for sensitive authentication data.
  • Never rely on Context alone to enforce backend security.
  • Test providers and consuming components independently.

17. Mini Project: Shared User Preferences Application

17.1 Project Overview

Create a React Native application that shares a user's display name and theme preference across multiple components using Context API.

17.2 Project Requirements

  • Create a PreferencesContext.
  • Store a display name in state.
  • Store the current theme in state.
  • Create a provider that exposes the values and update functions.
  • Create a Profile component that displays the user's name.
  • Create a ThemeToggle component that changes the theme.
  • Create a Header component that reads the same display name.
  • Update all relevant consumers when the context value changes.
  • Optionally persist preferences across app restarts.

17.3 Example Preferences Context

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

const PreferencesContext = createContext(null);

export function PreferencesProvider({ children }) {
  const [displayName, setDisplayName] = useState('Guest');
  const [theme, setTheme] = useState('light');

  function toggleTheme() {
    setTheme(current =>
      current === 'light' ? 'dark' : 'light'
    );
  }

  const value = useMemo(() => ({
    displayName,
    setDisplayName,
    theme,
    toggleTheme
  }), [displayName, theme]);

  return (
    <PreferencesContext.Provider value={value}>
      {children}
    </PreferencesContext.Provider>
  );
}

export function usePreferences() {
  const context = useContext(PreferencesContext);

  if (!context) {
    throw new Error(
      'usePreferences must be used inside PreferencesProvider'
    );
  }

  return context;
}

17.4 Create a Profile Component

import React from 'react';
import { View, Text } from 'react-native';
import { usePreferences } from './PreferencesContext';

export default function Profile() {
  const { displayName, theme } = usePreferences();

  return (
    <View>
      <Text>Name: {displayName}</Text>
      <Text>Theme: {theme}</Text>
    </View>
  );
}

17.5 Create a Theme Toggle Component

import React from 'react';
import { View, Button } from 'react-native';
import { usePreferences } from './PreferencesContext';

export default function ThemeToggle() {
  const { toggleTheme } = usePreferences();

  return (
    <View>
      <Button title="Change Theme" onPress={toggleTheme} />
    </View>
  );
}

17.6 Run the Project

import React from 'react';
import { View, Text } from 'react-native';
import { PreferencesProvider } from './PreferencesContext';
import Profile from './Profile';
import ThemeToggle from './ThemeToggle';

export default function App() {
  return (
    <PreferencesProvider>
      <View>
        <Text>User Preferences</Text>
        <Profile />
        <ThemeToggle />
      </View>
    </PreferencesProvider>
  );
}

17.7 Expected Result

The Profile component reads shared preferences from Context. When the theme changes, the consuming component receives the updated value and displays the new theme. The same provider can expose the display name to additional components without passing it through every intermediate component.

18. Exercises and Practice Tasks

  1. Create a context that stores a user's display name.
  2. Build a provider that shares a selected theme across three components.
  3. Create a custom Hook that reads a context and throws a useful error when its provider is missing.
  4. Build a shared counter using Context and useState.
  5. Build a shopping cart using Context and useReducer.
  6. Create an authentication context that exposes the current user and a sign-out function.
  7. Use multiple contexts for theme, authentication, and cart state.
  8. Test the behavior when a context consumer is rendered outside its provider.
  9. Identify unnecessary context value changes and apply memoization where useful.
  10. Extend the preferences project to restore non-sensitive preferences after restarting the app.

19. Interview Questions and Answers

Q1. What is the React Context API?

It is a React feature that allows components to share values with descendants without passing props through every intermediate component.

Q2. What is prop drilling?

Prop drilling is passing props through intermediate components that do not need to use the values themselves.

Q3. What is createContext?

createContext creates a context object that can provide and expose a value through the component tree.

Q4. What is a context provider?

A provider supplies a value to descendant components that read the matching context.

Q5. What is useContext?

useContext is a React Hook that reads the value from the closest matching context provider above the calling component.

Q6. What is the default context value?

It is the fallback value supplied to createContext. React uses it when no matching provider exists above the consumer.

Q7. Does Context automatically manage state?

No. Context provides values to components. To manage changing values, it is commonly combined with useState or useReducer.

Q8. What is the difference between Context and props?

Props explicitly pass data from a parent to a child, while Context makes a value available to descendants without requiring every intermediate component to pass it along.

Q9. Can multiple contexts be used in one application?

Yes. Separate contexts can manage authentication, themes, cart data, and other shared concerns.

Q10. Can Context be used for authentication?

Yes. It can share the current user and authentication actions, but backend endpoints must still enforce authentication and authorization.

Q11. Can Context be combined with useReducer?

Yes. A provider can expose reducer-managed state and its dispatch function to descendant components.

Q12. What is a custom context Hook?

It is a reusable Hook that wraps useContext and can provide a convenient interface or validate that the required provider exists.

Q13. Does Context persist data after an app restart?

No. Context state normally lives in memory. Persistent values require a separate storage and restoration mechanism.

Q14. How can Context performance be improved?

Keep contexts focused, avoid unnecessary provider value changes, and consider useMemo or useCallback when they provide measurable benefits.

Q15. Does React.memo prevent context-driven updates?

No. A component that reads a context still receives updates when that context's value changes, even if it is wrapped in React.memo.

Q16. Can Context replace every state-management library?

Not necessarily. Context works well for many shared values, but applications with more advanced state, selector, debugging, or data-caching requirements may need additional tools.

Q17. What happens when contexts are nested?

A consumer reads the value from the closest matching provider above it. An inner provider can override the value for its own descendants.

Q18. Why use separate contexts?

Separating unrelated state makes code easier to understand and can reduce updates caused by unrelated context changes.

Q19. What is the difference between Context and useState?

useState stores a component's state, while Context distributes a value to descendant components. They are often used together.

Q20. What is the difference between Context and useReducer?

useReducer centralizes state transition logic, while Context provides a way to share a value across the component tree. They solve different but complementary problems.

20. Quick Revision Checklist

  • Context helps share values without prop drilling.
  • createContext creates a context object.
  • A provider supplies the value to descendant components.
  • useContext reads the nearest matching provider value.
  • The default value is used only when no matching provider is found.
  • Context can be combined with useState or useReducer.
  • Custom Hooks make context consumption easier to reuse.
  • Multiple contexts can separate different application responsibilities.
  • Consumers can render again when their context value changes.
  • useMemo and useCallback can help optimize provider values when needed.
  • Context state does not automatically persist across app restarts.
  • Context is not a replacement for backend authentication and authorization.

21. Conclusion

The React Context API is a useful feature for sharing state and other values across components in a React Native application. It reduces prop drilling and helps centralize access to shared information such as user preferences, themes, authentication status, and shopping cart data.

By combining Context with useState for straightforward state or useReducer for more complex updates, developers can build a structured state-sharing solution. Good context design includes clear responsibilities, reusable custom Hooks, immutable updates, appropriate performance optimizations, and secure handling of sensitive information.

Context is most effective when used where shared access is genuinely needed, while local component state and ordinary props remain useful for simpler, localized data flow.

22. Additional Learning Resources

whatsapp