How State Management Solutions Are Implemented in Stremio-Web

Stremio-web implements state management through a lightweight React-centric architecture that combines React Contexts for global dependency injection, a custom useModelState hook for Core synchronization, and domain-specific wrapper hooks that provide declarative access to application state without external flux libraries.

Stremio-web relies on a custom state management architecture that leverages React primitives to synchronize UI components with the underlying Stremio Core engine. Unlike traditional React applications that might use Redux or MobX, the state management solutions implemented in stremio-web center on the useModelState hook and a minimal Context API pattern. This approach eliminates unnecessary overhead while maintaining performant, reactive updates across the component tree.

React Contexts for Global Services

Stremio-web uses React Contexts to provide immutable global objects—such as the Core services instance, routing focus flags, and modal-container references—to any component in the tree. These contexts are created with React.createContext and supplied by matching provider components.

The primary global context is defined in src/services/ServicesContext/ServicesContext.js, which exports a context for the services map. The concrete implementation is injected via src/services/ServicesContext/ServicesProvider.js, which wraps the application and supplies the Core, Chromecast, Gamepad, and other services. Additional contexts include RouteFocusedContext (src/router/RouteFocusedContext/RouteFocusedContext.js) for tracking route focus and ModalsContainerContext (src/router/ModalsContainerContext/ModalsContainerContext.js) for sharing modal handling across the app.

Components access these services through the useServices hook:

import { useServices } from 'stremio/services';

const Player = () => {
    const services = useServices();
    // services.core exposes the Stremio Core instance for low-level operations
    return <div>{/* Player implementation */}</div>;
};

The useModelState Hook Pattern

At the core of Stremio-web's state management is the useModelState hook, located in src/common/useModelState.js. This hook creates a reactive bridge between React components and the Stremio Core's model-based state architecture.

How useModelState Connects to the Core

The hook obtains the Stremio Core instance via useCore() and registers a throttled listener on the Core's state event. When the Core emits state updates, the hook filters them based on the requested model name (e.g., library, player, search) and optional dependencies. It retrieves the latest model state using core.transport.getState and optionally transforms the raw state through a user-supplied map callback. The processed state is stored in a useReducer-based container to ensure proper batching and deduplication.

The hook also manages lifecycle actions: it dispatches an initial action if supplied (typically to load data) and ensures an Unload action fires on unmount to free resources:

// src/common/useModelState.js (conceptual structure)
const useModelState = ({ model, action, map }) => {
    const core = useCore();
    const [state, dispatch] = useReducer(reducer, initialState);
    
    useEffect(() => {
        const listener = throttle(() => {
            const coreState = core.transport.getState();
            const modelSlice = coreState[model];
            dispatch({ type: 'UPDATE', payload: map ? map(modelSlice) : modelSlice });
        }, 100);
        
        core.on('state', listener);
        if (action) core.dispatch({ ...action, model });
        
        return () => {
            core.off('state', listener);
            core.dispatch({ action: 'Unload', model });
        };
    }, [model, action]);
    
    return state;
};

Core Suspension for Safe Initialization

Before any model access occurs, Stremio-web guarantees Core readiness through the CoreSuspender pattern (src/common/CoreSuspender.js). The useCoreSuspender() hook returns { getState, setState } methods that useModelState uses to safely read the initial snapshot. This prevents race conditions where components might attempt to access Core state before the transport layer is fully initialized.

Domain-Specific State Hooks

Rather than exposing useModelState directly throughout the UI, Stremio-web provides convenient wrapper hooks for specific features. Each follows the same declarative pattern: useModelState({ model: 'model_name', action, map }).

useLibrary (src/routes/Library/useLibrary.js) manages the user's content library:

import { useModelState } from 'stremio/common';

export const useLibrary = (action) =>
    useModelState({
        model: 'library',
        action,
        map: (state) => ({
            items: state.items ?? [],
            loading: state.loading ?? false,
        })
    });

usePlayer (src/routes/Player/usePlayer.js) handles playback state and stream selection.

useSearch (src/routes/Search/useSearch.js) manages search query state and results.

useEvents (src/components/EventModal/useEvents.js) connects to the ctx model for application-wide events.

These hooks demonstrate how useModelState enables component-level state shaping while maintaining a consistent interface to the Core.

Application Bootstrap Pattern

The top-level src/App/App.js orchestrates the state management architecture by wrapping the entire UI with ServicesProvider. This injection makes the Core and other services available to all descendants through React Context:

// src/App/App.js (simplified)
import { ServicesProvider } from 'stremio/services';
import { Router } from 'stremio/router';

const App = () => (
    <ServicesProvider>
        <Router>
            {/* Route components call useLibrary, usePlayer, etc. */}
        </Router>
    </ServicesProvider>
);

Route components then import their respective domain hooks (e.g., useLibrary for the Library route), which internally call useModelState. This keeps the UI reactive to any Core state change while avoiding prop drilling or heavyweight external state libraries.

Summary

  • React Contexts (ServicesContext, RouteFocusedContext, etc.) provide global service injection without prop drilling.
  • useModelState (src/common/useModelState.js) is the primary hook connecting components to Stremio Core models via throttled event listeners and useReducer.
  • CoreSuspender (src/common/CoreSuspender.js) ensures safe initialization before any state reads occur.
  • Domain hooks (e.g., useLibrary, usePlayer) wrap useModelState to provide type-safe, feature-specific state accessors.
  • The architecture avoids Redux or MobX, relying instead on React primitives and the Stremio Core's event-driven state machine.

Frequently Asked Questions

Does Stremio-web use Redux or MobX for state management?

No. According to the Stremio-web source code, the application avoids external flux libraries entirely. Instead, it uses a custom useModelState hook combined with React Contexts to synchronize components with the Stremio Core engine, resulting in a smaller bundle size and simpler mental model.

What is the useModelState hook used for?

The useModelState hook (src/common/useModelState.js) connects React components to specific models in the Stremio Core (such as library, player, or search). It handles subscription management, throttled updates, automatic loading/unloading of model data, and optional state transformation through a map callback.

How does Stremio-web handle Core initialization before state access?

Stremio-web implements a CoreSuspender pattern (src/common/CoreSuspender.js) that guarantees the Core transport layer is ready before any state reads occur. The useCoreSuspender() hook provides getState and setState methods that useModelState uses to safely retrieve the initial state snapshot, preventing race conditions during application startup.

Can I create custom state hooks for new features in Stremio-web?

Yes. New features should follow the established pattern of creating a wrapper hook around useModelState. Declare the hook in your feature directory (e.g., src/routes/MyFeature/useMyFeature.js), call useModelState with the appropriate model name and optional action and map parameters, and export the hook for use in your components. This maintains consistency with the existing useLibrary and usePlayer implementations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →