Skip to content

StorionState Management That Just Works

Write state naturally. Let Storion handle the rest.

Storion

See Storion in 30 Seconds

Here's a complete, working counter:

tsx
import { store, StoreProvider, useStore } from 'storion/react'

// STEP 1: Define your store ─────────────────────────────────────────────────
const counterStore = store({
  name: 'counter',          // Name appears in DevTools for easy debugging
  state: { count: 0 },      // Initial state — becomes reactive automatically

  // Setup runs once when store is first used
  setup({ state }) {
    return {
      // Actions: just mutate state directly!
      // Storion wraps state in a Proxy, so mutations trigger updates
      increment: () => { state.count++ },
      decrement: () => { state.count-- },
      reset: () => { state.count = 0 },
    }
  },
})

// STEP 2: Use in React ──────────────────────────────────────────────────────
function Counter() {
  // The selector function receives a context with `get` to access stores
  const { count, increment, decrement } = useStore(({ get }) => {
    // get() returns [state, actions] tuple
    const [state, actions] = get(counterStore)

    // Return only what this component needs
    // Storion tracks that we read `state.count` — so this component
    // ONLY re-renders when `count` changes, not other state
    return {
      count: state.count,
      increment: actions.increment,
      decrement: actions.decrement,
    }
  })

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

// STEP 3: Wrap with Provider ────────────────────────────────────────────────
function App() {
  return (
    <StoreProvider>
      <Counter />
    </StoreProvider>
  )
}
Don't like Providers? Use create() instead
tsx
import { create } from 'storion/react'

const [counter, useCounter] = create({
  state: { count: 0 },
  setup({ state }) {
    return {
      increment: () => { state.count++ },
      decrement: () => { state.count-- },
    }
  },
})

// No Provider needed!
function Counter() {
  const { count, increment, decrement } = useCounter((state, actions) => ({
    count: state.count,
    increment: actions.increment,
    decrement: actions.decrement,
  }))

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

What's Happening Here?

What You WriteWhat Storion Does
state.count++Detects mutation → notifies subscribers
state.count in selectorTracks this property → re-renders when it changes
StoreProviderManages store instances and enables sharing across components

Copy, paste, run

The example above works as-is. Just npm install storion and try it.


Why Storion?

The Problem with Other Libraries

tsx
// ❌ Zustand: One store per hook, manual selectors, memoize actions
function Component({ step }) {
  // Returning object? Need shallow compare or it re-renders every time!
  const { count, name } = useCounterStore(
    (state) => ({ count: state.count, name: state.name }),
    shallow  // forget this? re-renders on EVERY state change!
  )
  
  // Action uses props → need useCallback with deps
  const increment = useCallback(() => {
    useCounterStore.getState().incrementBy(step)
  }, [step])  // forget this dep? stale closure bug!
  
  return <ChildComponent onIncrement={increment} />
}

// ✅ Storion: Tracks state access, not selector result
function Component({ step }) {
  const { count, name, increment } = useStore(({ get }) => {
    const [counter, counterActions] = get(counterStore)
    const [user] = get(userStore)
    return {
      count: counter.count,    // ← Storion tracks this access
      name: user.name,         // ← and this access
      increment: () => counterActions.incrementBy(step),  // ← auto-stable!
    }
  })
  // No shallow compare — we track state.count and state.name, not result object
  // increment is auto-stabilized — same reference, always fresh `step` value
  return <ChildComponent onIncrement={increment} />
}

Feature Comparison

FeatureReduxZustandJotaiStorion
Auto-tracking❌ Manual selectors❌ Manual selectors
Multiple stores✅ Single store⚠️ One hook each✅ AtomsOne hook
Cross-store deps⚠️ Verbose❌ ManualAuto-resolved
Stable actions⚠️ useCallback⚠️ Extra selectors⚠️Automatic
Object selectors⚠️ Reselect⚠️ Need shallowNo compareFn
TypeScript⚠️ Verbose✅ Good✅ Good✅ Excellent
Dependency Injection✅ Built-in
Async State❌ External lib⚠️ Basic⚠️ Basic✅ First-class
Middleware✅ Complex setup⚠️ Per-store onlyGlobal + per-store
DevTools⚠️
Bundle Size~2KB~1KB~2KB~4KB
Learning CurveSteepEasyMediumEasy

Beyond the Basics

Storion grows with your app. Here's a taste of what's possible:

Async Data Fetching

tsx
import { async } from 'storion/async'

const userStore = store({
  name: 'user',
  state: {
    // async.fresh() = undefined while loading, data after success
    user: async.fresh<User>(),
  },
  setup({ focus }) {
    // Create an async action bound to state.user
    const userQuery = async(
      focus('user'),  // Links this action to state.user
      async (ctx, id: string) => {
        // ctx.signal auto-cancels on unmount or new request
        const res = await fetch(`/api/users/${id}`, { signal: ctx.signal })
        return res.json()
      }
    )

    return { fetchUser: userQuery.dispatch }
  },
})

Cross-Store Dependencies

tsx
const cartStore = store({
  name: 'cart',
  state: { items: [] },
  setup({ state, get }) {
    // Access other stores in setup
    const [userState] = get(userStore)

    return {
      checkout: async () => {
        // Use user state in cart actions
        await api.checkout(userState.user.data?.id, state.items)
      },
    }
  },
})

Persistence (One Line)

tsx
import { persist } from 'storion/persist'

const settingsStore = store({
  name: 'settings',
  state: { theme: 'dark', fontSize: 14 },
  meta: persist(),  // ← Auto-saves to localStorage
})

Ready to learn more?

These features are covered in the Getting Started guide.


Live Demos

See Storion in action:

  • 🎮 Feature Showcase — All major features demonstrated
  • 🐾 Pokemon App — API integration with caching
  • 💬 Chat App — Real-time with IndexedDB persistence
  • 💰 Expense Manager — Clean architecture example

View All Demos →


Get Started

📖 Read the Guide — Step-by-step tutorial

📚 API Reference — Detailed API documentation

💻 Try the Demos — Interactive examples


Sponsors

Become a sponsor to support Storion development!

Released under the MIT License.