Skip to content

Sharing State with Context in React

Learn how to share data throughout your application using both React's Context API and Anchor's Global Context.

What You'll Learn

In this tutorial, you'll learn:

  1. How to integrate React's Context API with Anchor for dependency injection.
  2. How to use Anchor's Global Context for dependency injection.
  3. When to use each approach for optimal state management.

Understanding Context Systems

Context systems allow you to share data throughout your application without prop drilling. Both Anchor and React provide powerful context mechanisms, and they can be used together to create flexible and scalable applications.

React's Context API

React's Context API is a built-in feature that allows you to share values between components without explicitly passing a prop through every level of the tree. This is the preferred method for dependency injection in most cases.

Anchor's Global Context

Anchor's Global Context is a reactive dependency injection system that allows you to store and retrieve values by key. It's particularly useful for sharing services, configurations, or global state throughout your application.

Anchor's global context is intended for easy context sharing without needing to create a specific context and wrap the component using the Context Provider. The constraint is, it's a global context which means the whole app shares the same place.

WARNING

Anchor's global context doesn't support NextJS server components at the moment. We will try to support it, but it's not a priority at the moment.

Using React's Context API

React's Context API is perfect for sharing values that don't change frequently or when you want to leverage React's built-in context system. When using React's context with Anchor, you pass the reactive state directly to the context, which means you don't need to manually update the context value - it automatically updates when the state changes.

tsx
import '@tailwindcss/browser';
import React, { createContext, useContext } from 'react';
import { observer, useAnchor } from '@anchorlib/react';

interface Settings {
  theme: 'light' | 'dark';
  language: 'en' | 'fr';
}

// Create React context
const AppSettings = createContext<Settings>({
  theme: 'light',
  language: 'en',
});

// Component that uses the context.
const ThemedButton = observer(() => {
  // Get the current theme from the context.
  const { theme } = useContext(AppSettings);

  return (
    <button
      style={{
        background: theme === 'dark' ? '#333' : '#fff',
        color: theme === 'dark' ? '#fff' : '#000',
        padding: '10px 20px',
        border: '1px solid #ccc',
        borderRadius: '4px',
      }}>
      Themed Button ({theme})
    </button>
  );
});

// Main app component
const App = () => {
  const [settings] = useAnchor<Settings>({
    theme: 'light',
    language: 'en',
  });

  const toggleTheme = () => {
    settings.theme = settings.theme === 'light' ? 'dark' : 'light';
  };

  return (
    <div className="flex flex-col items-center justify-center gap-6 w-screen h-screen">
      <h1>React Context Example</h1>
      <p className="text-center px-10">
        This example demonstrates how to use Anchor with React context to share state between components.
      </p>
      <AppSettings value={settings}>
        <ThemedButton />
      </AppSettings>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};

export default App;
Try it Yourself
export default function App(): JSX.Element {
  return <h1>Hello world</h1>
}

In This Example

  1. We create a React context using createContext()
  2. We build a custom hook useSettingsContext() for easier consumption
  3. We use useAnchor to manage the reactive state
  4. We pass the reactive state directly to React's context provider using the value prop
  5. Child components consume the context using useContext() or our custom hook
  6. When the Anchor state changes, React's context automatically updates

Using Anchor's Global Context

Anchor's Global Context provides a reactive Map that can be used to store and retrieve values by key. Let's see how to use it in a React application.

tsx
import '@tailwindcss/browser';
import { getContext, setContext } from '@anchorlib/core';
import { observer, useAnchor } from '@anchorlib/react';
import React from 'react';

interface Settings {
  theme: 'light' | 'dark';
  language: 'en' | 'fr';
}

// A component that consumes the Anchor context.
const ThemedButton = observer(() => {
  // Get the current settings from Anchor context.
  const settings = getContext<Settings>('settings', { theme: 'light', language: 'en' });

  return (
    <button
      style={{
        background: settings.theme === 'dark' ? '#333' : '#fff',
        color: settings.theme === 'dark' ? '#fff' : '#000',
        padding: '10px 20px',
        border: '1px solid #ccc',
        borderRadius: '4px',
      }}>
      Themed Button ({settings.theme})
    </button>
  );
});

// Main app component
const App = () => {
  const [settings] = useAnchor<Settings>({
    theme: 'light',
    language: 'en',
  });
  setContext('settings', settings);

  const toggleTheme = () => {
    settings.theme = settings.theme === 'light' ? 'dark' : 'light';
  };

  return (
    <div className="flex flex-col items-center justify-center gap-6 w-screen h-screen">
      <h1>Anchor Context Example</h1>
      <p className="text-center px-10">
        This example demonstrates how to use Anchor's global context to share state between components.
      </p>
      <ThemedButton />
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};

export default App;
Try it Yourself
export default function App(): JSX.Element {
  return <h1>Hello world</h1>
}

In This Example

  1. We use useAnchor to create a reactive state object
  2. We directly set values in the context using setContext()
  3. We retrieve values using getContext()
  4. The context values are reactive and will update when the Anchor state changes
  5. We don't need a provider component as with React's Context API

Combining Both Context Systems

You can combine both context systems to leverage the strengths of each. Use React's Context API for UI-related values and Anchor's Global Context for services and cross-cutting concerns.

tsx
import '@tailwindcss/browser';
import React, { createContext, useContext } from 'react';
import { getContext, setContext } from '@anchorlib/core';
import { observer, useAnchor } from '@anchorlib/react';

// Define our state interfaces
interface UIState {
  theme: 'light' | 'dark';
  sidebarOpen: boolean;
}

// React context for UI state
const UIContext = createContext<UIState>({
  theme: 'light',
  sidebarOpen: false,
});

// Service that might be shared across the app
class UserService {
  getCurrentUser() {
    return {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com',
    };
  }
}

// Custom hook for UI context
const useUIContext = () => {
  return useContext(UIContext);
};

// Component using React context
const Header = observer(() => {
  const { theme } = useUIContext();

  return (
    <header
      className="w-full p-4 border-b"
      style={{
        background: theme === 'dark' ? '#333' : '#f5f5f5',
        borderColor: theme === 'dark' ? '#555' : '#ddd',
      }}>
      <h1 className="text-xl font-bold">My App</h1>
    </header>
  );
});

// Component using Anchor context
const UserProfile = observer(() => {
  // Get user service from Anchor context
  const userService = getContext<UserService>(UserService.name);
  const user = userService?.getCurrentUser();

  if (!user) return <div>No user found</div>;

  return (
    <div className="p-4">
      <h2 className="text-lg font-semibold">Welcome, {user.name}!</h2>
      <p className="text-gray-600">Email: {user.email}</p>
    </div>
  );
});

// Main app component
const App = () => {
  const [uiState] = useAnchor<UIState>({
    theme: 'light',
    sidebarOpen: false,
  });

  const toggleTheme = () => {
    uiState.theme = uiState.theme === 'dark' ? 'light' : 'dark';
  };

  // Provide user service through Anchor context
  const userService = new UserService();
  setContext(UserService.name, userService);

  const ThemeToggle = observer(() => {
    return (
      <button
        onClick={toggleTheme}
        className="px-4 py-2 rounded border"
        style={{
          background: uiState.theme === 'dark' ? '#444' : '#fff',
          color: uiState.theme === 'dark' ? '#fff' : '#000',
          borderColor: uiState.theme === 'dark' ? '#666' : '#ccc',
        }}>
        Switch to {uiState.theme === 'dark' ? 'Light' : 'Dark'} Mode
      </button>
    );
  });

  return (
    <div className="flex flex-col min-h-screen">
      <UIContext value={uiState}>
        <Header />
        <main className="flex-grow p-4">
          <div className="max-w-2xl mx-auto">
            <h1 className="text-2xl font-bold mb-6">Combined Context Example</h1>
            <p className="mb-6">This example shows how to combine React's Context API with Anchor's Global Context.</p>
            <UserProfile />
            <div className="mt-6">
              <ThemeToggle />
            </div>
          </div>
        </main>
      </UIContext>
    </div>
  );
};

export default App;
Try it Yourself
export default function App(): JSX.Element {
  return <h1>Hello world</h1>
}

In This Example

  1. We use React's Context API for UI-related state (theme, sidebar state)
  2. We use Anchor's Global Context for services (UserService)
  3. We pass reactive state directly to React's context provider using the value prop
  4. We directly set services in Anchor's context using setContext
  5. Different components consume the appropriate context based on their needs
  6. Both context systems automatically update when the Anchor state changes

Key Points for Context Sharing

  1. Use React Context for UI state: Theme, user preferences, and other UI-related values
  2. Use Anchor Context for services: API clients, utilities, and cross-cutting concerns
  3. Combine both when needed: Leverage the strengths of each system
  4. Keep context values stable: Avoid creating new objects on every render
  5. Use custom hooks: Encapsulate context consumption logic in custom hooks for better reusability
  6. Pass reactive state to React context: When using React's context with Anchor, pass the reactive state directly to avoid manual updates