# How the Plane React Frontend Manages Global State Using MobX Stores

> Explore how the Plane React frontend uses MobX stores from shared-state to manage global state, integrating React Context and the observer pattern for efficient reactivity.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: deep-dive
- Published: 2026-06-22

---

**The Plane React frontend centralizes global state in a single `CoreRootStore` that aggregates MobX stores from the `@plane/shared-state` package, providing them to components via React Context while using the `observer` pattern from `mobx-react` for fine-grained reactivity.**

The open-source project management tool Plane (makeplane/plane) implements a scalable state management architecture for its Next.js React frontend. By leveraging MobX observable stores within a dedicated `@plane/shared-state` workspace package, the application achieves type-safe, modular global state that eliminates prop-drilling and optimizes re-renders across the component tree.

## The @plane/shared-state Package Structure

The `@plane/shared-state` package is a workspace-level module that houses reusable MobX stores for data requiring cross-cutting UI access, such as work-item filters and rich-filter instances. This package acts as a centralized source for complex state logic that must remain agnostic of specific view implementations.

The package's entry point re-exports all store utilities from its internal `store` directory:

```typescript
// packages/shared-state/src/index.ts
export * from "./store";
export * from "./utils";

```

This structure allows the web application to import specific store classes and types without referencing internal file paths, maintaining clean dependency boundaries across the monorepo.

## MobX Store Architecture and Patterns

Each store within the shared-state package follows a consistent three-part MobX pattern: **observable state**, **actions**, and **computed functions**.

In [`packages/shared-state/src/store/work-item-filters/filter.store.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/store/work-item-filters/filter.store.ts), the `WorkItemFilterStore` class demonstrates this architecture:

```typescript
import { action, makeObservable, observable } from "mobx";
import { computedFn } from "mobx-utils";

export class WorkItemFilterStore implements IWorkItemFilterStore {
  filters: Map<TWorkItemFilterKey, IWorkItemFilterInstance>;

  constructor() {
    this.filters = new Map();
    makeObservable(this, {
      filters: observable,
      getOrCreateFilter: action,
      resetExpression: action,
      // …
    });
  }

  // Computed getter – memoised with MobX‑utils
  getFilter = computedFn((entityType, entityId) =>
    this.filters.get(this._getFilterKey(entityType, entityId))
  );

  // …
}

```

The `makeObservable` call explicitly defines which properties are observable and which methods are actions, ensuring strict mutation tracking. Computed values leverage `computedFn` from `mobx-utils` to maintain referential stability and prevent unnecessary re-renders in consuming components.

## The Root Store Pattern: Aggregating Global State

The web application creates a single `CoreRootStore` instance that aggregates every domain-specific store, including those imported from the shared-state package. This singleton pattern ensures all global mutable state exists within one cohesive object graph.

In [`apps/web/core/store/root.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/root.store.ts), the root store instantiates the shared `WorkItemFilterStore`:

```typescript
import { WorkItemFilterStore } from "@plane/shared-state";

export class CoreRootStore {
  // …many other stores
  workItemFilters: IWorkItemFilterStore;

  constructor() {
    // …initialize other stores
    this.workItemFilters = new WorkItemFilterStore();   // <-- shared‑state store
  }
}

```

This aggregation pattern allows the application to initialize all global state at application startup, ensuring consistent state availability throughout the component lifecycle.

## Providing Stores via React Context

To eliminate prop-drilling, Plane implements a React Context that holds the `CoreRootStore` instance. The `StoreContext` in [`apps/web/core/lib/store-context.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/lib/store-context.tsx) creates a global reference accessible to any component in the tree:

```typescript
import { createContext } from "react";
import { RootStore } from "@/plane-web/store/root.store";

export let rootStore = new RootStore();

export const StoreContext = createContext<RootStore>(rootStore);

export function StoreProvider({ children }: { children: ReactElement }) {
  return <StoreContext.Provider value={rootStore}>{children}</StoreContext.Provider>;
}

```

The `StoreProvider` wraps the entire application in [`apps/web/app/provider.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/app/provider.tsx), ensuring every component can access the root store via `useContext(StoreContext)`. For server-side rendering compatibility, the implementation uses `enableStaticRendering` to disable MobX reactions on the server, preventing memory leaks during Next.js SSR hydration.

## Consuming Stores in React Components

Components access specific stores by destructuring from the root store context. To maintain reactive updates, components that read observable state must be wrapped with the `observer` HOC from `mobx-react`.

For example, in [`apps/web/core/components/work-item-filters/filters-row.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/work-item-filters/filters-row.tsx):

```typescript
import { observer } from "mobx-react";
import type { IWorkItemFilterInstance } from "@plane/shared-state";

type Props = {
  filter: IWorkItemFilterInstance;
  // …
};

export const WorkItemFiltersRow = observer(function WorkItemFiltersRow({ filter, ...rest }: Props) {
  return <FiltersRow filter={filter} {...rest} />;
});

```

The `observer` wrapper ensures that any changes to the `filter` instance's observable properties automatically trigger component re-renders. Components typically access stores through custom hooks that abstract the context consumption:

```typescript
function useWorkItemFilter(entityType, entityId) {
  const { workItemFilters } = useContext(StoreContext);
  
  const filter = workItemFilters.getOrCreateFilter({
    entityType,
    entityId,
    showOnMount: true,
    onExpressionChange: (expr) => console.log("new expression", expr),
  });

  return filter;
}

```

## Summary

- **Centralized State**: All global state resides in a single `CoreRootStore` that aggregates domain-specific MobX stores, including those from `@plane/shared-state`.
- **Explicit Observability**: Stores use `makeObservable` to declare observable properties and actions, while `computedFn` memoizes derived values.
- **Context Distribution**: The `StoreContext` React context eliminates prop-drilling by providing the root store to any component in the tree.
- **Reactive Components**: The `observer` HOC from `mobx-react` binds components to observable state changes, enabling fine-grained re-renders only when accessed data mutates.
- **SSR Safety**: `enableStaticRendering` ensures MobX reactions are disabled during server-side rendering, preventing memory leaks in Next.js applications.

## Frequently Asked Questions

### How does the React frontend access MobX stores from the shared-state package?

The React frontend accesses MobX stores through a centralized `CoreRootStore` instance that aggregates all global stores, including those from `@plane/shared-state`. This root store is provided to the component tree via React Context (`StoreContext`), allowing any component to import the context and access specific stores like `workItemFilters` using the `useContext` hook.

### What is the purpose of the `observer` wrapper in Plane's React components?

The `observer` wrapper from `mobx-react` transforms React components into reactive observers that automatically re-render when MobX observable properties change. Without this wrapper, components would read initial state values but fail to update when mutations occur, breaking the reactive data flow between the stores and the UI.

### Why does Plane use `computedFn` instead of standard MobX `computed` decorators?

Plane uses `computedFn` from `mobx-utils` for parameterized computations that require memoization, such as the `getFilter` method in `WorkItemFilterStore`. Standard `computed` decorators work best for property getters, while `computedFn` optimizes method calls with arguments by caching results based on input parameters, preventing unnecessary recalculations and re-renders.

### How does the shared-state architecture support server-side rendering?

The architecture supports SSR through `enableStaticRendering`, which disables MobX's reactive tracking system during server-side execution. This prevents the accumulation of reaction disposables that could cause memory leaks during Next.js server-side rendering. The same root store structure works on both server and client, with reactions only activating once hydrated in the browser.