Skip to content

createContext: scoped store provider factory

At a glance

  • One export: createContext — a factory pairing a Zustand store with a scoped Provider/useStore.
  • Used by file-viewer for its per-instance store.
  • Not used by plugins — its state is a genuinely global singleton instead.
  • Calling useStore outside its Provider throws immediately, it never returns undefined.

packages/context exports exactly one thing: createContext. It's a factory that takes a function for building a Zustand store, and returns a matched Provider / useStore pair, scoped to whatever subtree the Provider wraps.

The problem it solves

A plain Zustand create() call gives you one store for the lifetime of the module — a singleton. That's fine for state that really is global (see how plugins does this below), but wrong for state that should be reset or duplicated per mounted instance — for example, one file-viewer store per file being viewed, not one store shared by every file-viewer on the page.

createContext closes over a createStore function you provide, and lazily constructs one store instance per Provider mount (via a useRef, so it survives re-renders but not remounts). It then hands back:

  • Provider — a component that takes your store's init props plus children, builds the store on first render, and puts it on a React Context.
  • useStore — a hook, usable by any descendant of Provider, that reads from that Context and subscribes to the store with the same (selector, equalityFn) signature Zustand's own hooks use. Calling it outside a Provider throws immediately rather than silently returning undefined.

Who uses it

file-viewer is the one package here that builds its state this way: context/file.ts calls createContext with a function that builds the file-viewer's { ui, file, path } store (createPackageStore), and exports the result as FileProvider / useStore. Each <FileProvider> in the app gets its own independent store — which is what you want when more than one file view could be mounted at once.

Contrast this with plugins, which does not use createContext: it keeps one global Zustand store (useGlobalPluginStore, built with a plain create() call) shared across the whole app, plus a separate plain React.createContext for a bag of host callback functions (PluginProviderCtx) that plugins call into. That split is deliberate there — plugin state genuinely is global (which plugins are registered, their logs, their progress), so there's no per-subtree instance to scope.

Rule of thumb

If a package's state should exist once per mounted instance of something, reach for @morph-mapper/context. If it's truly singleton, a plain Zustand create() is simpler — that's what plugins does.