Skip to content

React Integration

@hurum/core is framework-agnostic. It knows nothing about React, Vue, or any UI library. Stores expose a subscribe() + getState() interface — the standard contract for external state.

@hurum/react bridges that interface to React. Specifically, it:

  1. Prevents tearing. React 18 can interrupt and resume renders (concurrent mode). Without useSyncExternalStore, two components reading from the same store could see different states within a single render pass. @hurum/react uses useSyncExternalStore for every subscription, so this never happens.

  2. Provides granular subscriptions. Hurum stores contain many fields. If your component only reads count, it should not re-render when multiplier changes. The .use.count() hook subscribes to exactly one field — structural equality per field, not the entire state object.

  3. Manages store lifecycles. Some stores are global (one per app). Others are scoped (one per page, one per list item, one per modal). @hurum/react provides both patterns: singleton fallback via useStore(Def) and scoped instances via StoreProvider.

@hurum/react provides three things:

import { useStore, StoreProvider, withProvider } from '@hurum/react'
ExportDescription
useStore(Def)Context-aware hook: inside StoreProvider returns scoped instance, outside falls back to singleton
useStore(instance)Uses the given StoreInstance directly
StoreProviderReact context Provider for scoped instances
withProvider(Def, Component)HOC that auto-creates a scoped instance on mount

No extra wrapping step is needed. You import the store definition from @hurum/core and use it directly with these APIs.

React apps need both global and scoped state. Hurum supports both without forcing you to choose one upfront.

For state that is truly app-wide (auth, theme, feature flags), you want one instance shared across all components. No Provider ceremony, no context plumbing. Simply call useStore(Def) outside of any StoreProvider and it falls back to the global singleton:

import { useStore } from '@hurum/react'
import { AuthStore } from './store'
function UserMenu() {
const store = useStore(AuthStore)
const user = store.use.currentUser()
const loggedIn = store.use.isLoggedIn() // computed
if (!loggedIn) return <LoginButton />
return <span>Hello, {user.name}</span>
}

When there is no StoreProvider for AuthStore in the component tree, useStore(AuthStore) creates and returns a global singleton, lazily on first access. No setup required.

Context-aware — useStore + StoreProvider

Section titled “Context-aware — useStore + StoreProvider”

For state that is not global — a purchase form, a chart widget, a nested store with child items — you need independent instances. StoreProvider creates a scoped boundary; useStore() resolves the nearest instance.

import { useStore, StoreProvider } from '@hurum/react'
import { PurchaseStore } from './store'
function PurchaseContent() {
const store = useStore(PurchaseStore)
const purchase = store.use.purchase()
const saving = store.use.saving()
return (
<div>
<h2>{purchase?.name}</h2>
{saving && <p>Saving...</p>}
<button onClick={() => store.send.submitClicked({ purchase })}>
Submit
</button>
</div>
)
}

useStore() checks for a StoreProvider in the component tree. If found, it returns the scoped instance. If not, it falls back to the global singleton. This means the same component works in both contexts — you decide the boundary from the outside.

ScenarioPatternWhy
Auth, theme, feature flagsSingleton fallbackOne instance per app. Never needs isolation.
Page-level state with server dataStoreProvider + useStoreEach page needs its own initial state and deps.
Reusable component (charts, forms, widgets)StoreProvider + useStoreSame component, multiple independent instances.
Nested stores (Nested.array, Nested.map)StoreProvider + useStoreParent and children need scoped lifecycle.
SSRStoreProvider + Store.create()Server has no global. Each request = new instance.
Quick prototypeSingleton fallbackLess ceremony. Switch to StoreProvider when needed.

Start with singleton fallback for app-wide stores. Use StoreProvider the moment you need multiple instances, server data, or nested stores.