Logo
Published on

Multiple Contexts

How Multiple Contexts Organize Application State

Multiple Contexts pattern separates concerns by creating dedicated contexts for different domains of application state. This approach prevents context value bloat while enabling components to subscribe only to relevant data updates, leading to better performance and more maintainable codebases.

TL;DR

  • Use separate contexts for different state domains
  • Multiple Contexts prevents unnecessary re-renders
  • Enables granular control over state subscriptions
  • Perfect for large applications with complex state
const result = process(data)

The Monolithic Context Challenge

Your application stores all state in a single massive context object. Components re-render unnecessarily when unrelated state changes, and the context provider becomes a bottleneck. Adding new features requires modifying the central context structure.

// Single context with all application state
const React = require('react')
const AppContext = React.createContext()

function AppProvider({ children }) {
  const state = {
    user: { name: 'John', role: 'admin' },
    theme: 'dark',
    cart: { items: [], total: 0 },
  }
  console.log('Monolithic context causes unnecessary re-renders')
  return React.createElement(AppContext.Provider, { value: state }, children)
}
console.log('AppProvider created with single monolithic context')
AppProvider({ children: null })

Multiple Contexts separate concerns by domain, allowing components to subscribe only to relevant updates:

// Separate contexts for different domains
const React = require('react')
const UserContext = React.createContext()
const ThemeContext = React.createContext()
const CartContext = React.createContext()

function CombinedProvider({ children }) {
  const user = { name: 'John', role: 'admin' }
  const theme = 'dark'
  console.log('Multiple contexts enable selective subscriptions')
  return React.createElement(
    UserContext.Provider,
    { value: user },
    React.createElement(ThemeContext.Provider, { value: theme }, children)
  )
}

Best Practises

Use multiple contexts when:

  • ✅ Different state domains update at different frequencies
  • ✅ Components need to subscribe to specific state slices only
  • ✅ Building large applications with complex state management
  • ✅ Want to avoid unnecessary re-renders from unrelated state changes

Avoid when:

  • 🚩 Application has simple, tightly-coupled state
  • 🚩 All state updates happen simultaneously
  • 🚩 Provider nesting becomes too deep (use composition)
  • 🚩 Contexts have frequent cross-dependencies

System Design Trade-offs

AspectMultiple ContextsSingle Context
PerformanceExcellent - selective re-rendersPoor - all consumers re-render
MaintainabilityHigh - domain separationLow - monolithic state
ComplexityMedium - provider compositionLow - single provider
TestingEasy - isolate domain contextsHard - test entire state
ScalabilityExcellent - add contexts independentlyPoor - single point of change
Re-usabilityHigh - contexts are focusedLow - tightly coupled state

More Code Examples

❌ Monolithic context anti-pattern
// Single massive context causing performance issues
const React = require('react')
const { createContext } = React

const AppContext = createContext()

function AppProvider({ children }) {
  // Simulate problematic monolithic state
  const appState = {
    user: { id: 1, name: 'Alice', preferences: { theme: 'dark' } },
    products: [{ id: 1, name: 'Laptop', price: 999 }],
    cart: { items: [], total: 0 },
    ui: { loading: false, modal: null },
    notifications: [],
  }

  console.log('AppProvider re-rendering with all state')

  const updateUser = (updates) => {
    console.log('Would update user:', updates)
  }

  const updateCart = (items) => {
    console.log('Would update cart:', items)
  }

  return React.createElement(
    AppContext.Provider,
    { value: { ...appState, updateUser, updateCart } },
    children
  )
}

// Components re-render when any state changes
function ProductList() {
  // Simulate what useContext would provide from monolithic context
  const products = [{ id: 1, name: 'Laptop', price: 999 }]
  const user = { name: 'Alice' }
  console.log('ProductList re-rendered due to user change')
  const items = products.map((p) =>
    React.createElement('div', { key: p.id }, p.name + ': $' + p.price)
  )
  return React.createElement('div', null, items)
}

console.log('Single context causes cascade re-renders')
ProductList() // Test the component
✅ Domain separation with contexts
// Separated contexts for better performance and maintainability
const React = require('react')
const { createContext } = React

// Domain-specific contexts
const UserContext = createContext()
const ProductContext = createContext()
const CartContext = createContext()

// Individual providers
function UserProvider({ children }) {
  const user = { id: 1, name: 'Alice', preferences: { theme: 'dark' } }
  console.log('UserProvider state:', user.name)
  return React.createElement(UserContext.Provider, { value: user }, children)
}

function ProductProvider({ children }) {
  const products = [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Mouse', price: 29 },
  ]
  console.log('ProductProvider has', products.length, 'products')
  return React.createElement(ProductContext.Provider, { value: products }, children)
}

// Component only re-renders when products change
function ProductList() {
  // Simulate what useContext would provide from ProductContext
  const products = [{ id: 1, name: 'Laptop', price: 999 }]
  console.log('ProductList rendering products only')
  const items = products.map((p) =>
    React.createElement('div', { key: p.id }, p.name + ': $' + p.price)
  )
  return React.createElement('div', null, items)
}

// Root App with composed providers
function App() {
  return React.createElement(
    UserProvider,
    null,
    React.createElement(ProductProvider, null, React.createElement(ProductList))
  )
}

console.log('Multiple contexts prevent unnecessary re-renders')
console.log('Each context handles one domain efficiently')
console.log('Components subscribe only to relevant data')
ProductList() // Test the component

Technical Trivia

The Slack Performance Investigation of 2020: Slack's engineering team discovered that their React app was re-rendering thousands of components unnecessarily. The culprit was a single massive context containing user data, workspace settings, and channel information all in one object.

Why performance degraded: Every keystroke in a message input triggered context updates that caused the entire sidebar, member list, and channel history to re-render. The monolithic context meant components couldn't subscribe to just the data they needed.

The multiple contexts solution: Slack split their state into UserContext, WorkspaceContext, and ChannelContext. Components now re-render only when their specific domain data changes. This reduced unnecessary renders by 85% and significantly improved typing performance in large workspaces.


Master Multiple Contexts: Implementation Strategy

Design contexts around business domains rather than technical layers. Create separate contexts for user data, application settings, and feature-specific state. Use provider composition at the app root and custom hooks to simplify context consumption. This pattern scales well as applications grow and enables teams to work on different domains independently.