Logix

Migrating from Zustand

A step-by-step guide to migrate from Zustand to Logix.

This guide helps you migrate an existing Zustand-based state management setup to Logix incrementally.

Concept mapping

ZustandLogixNotes
createStoreLogix.Module.makedefine state shape
StoreModulestate container
StateState Schematypes defined via Schema
Actions (in store)Actions + Reducers/Logicseparate declaration/behavior
set(state)$.state.update/mutateupdate state
get()$.state.readread state
SelectorsuseSelector(module, fn)derive + subscribe
MiddlewareLogic + Flowasync/effects
useStoreuseModuleReact integration

Migration example

Original Zustand code

import { create } from 'zustand'

interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  incrementAsync: () => Promise<void>
}

const useCounterStore = create<CounterState>((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  incrementAsync: async () => {
    await new Promise((r) => setTimeout(r, 1000))
    set((state) => ({ count: state.count + 1 }))
  },
}))

// Usage
function Counter() {
  const { count, increment } = useCounterStore()
  return <button onClick={increment}>{count}</button>
}

After migrating to Logix

import * as Logix from '@logixjs/core'
import { Effect, Schema } from 'effect'

// 1) Define a Module (separate declaration and behavior)
const CounterDef = Logix.Module.make('Counter', {
  state: Schema.Struct({ count: Schema.Number }),
  actions: {
    increment: Schema.Void,
    decrement: Schema.Void,
    incrementAsync: Schema.Void,
  },
  // Sync logic can be expressed as reducers
  reducers: {
    increment: (s) => ({ ...s, count: s.count + 1 }),
    decrement: (s) => ({ ...s, count: s.count - 1 }),
  },
})

// 2) Put async logic into Logic
const CounterLogic = CounterDef.logic(($) =>
  Effect.gen(function* () {
    yield* $.onAction('incrementAsync').run(() =>
      Effect.gen(function* () {
        yield* Effect.sleep(1000)
        yield* $.state.mutate((d) => {
          d.count++
        })
      }),
    )
  }),
)

// 3) Assemble
const CounterModule = CounterDef.implement({
  initial: { count: 0 },
  logics: [CounterLogic],
})
// React usage
import { useModule, useSelector, useDispatch } from '@logixjs/react'

function Counter() {
  const counter = useModule(CounterModule)
  const count = useSelector(counter, (s) => s.count)
  const dispatch = useDispatch(counter)

  return <button onClick={() => dispatch({ _tag: 'increment' })}>{count}</button>
}

Incremental migration strategy

1. Run in parallel

Keep existing Zustand stores and introduce a Logix Runtime in parallel:

function App() {
  return (
    <RuntimeProvider runtime={logixRuntime}>
      {/* New features use Logix */}
      <NewFeature />
      {/* Legacy features keep using Zustand; migrate gradually */}
      <LegacyFeature />
    </RuntimeProvider>
  )
}

2. Migrate one store at a time

For each store:

  1. Create the corresponding Module.make definition
  2. Move Action implementation into reducers + Logic
  3. Replace useStore with useModule in components
  4. After tests/verification, delete the old store

3. Shared state (transition period)

If you need to share state during migration:

// Read Zustand inside Logix Logic
const BridgeLogic = NewModuleDef.logic(($) =>
  Effect.gen(function* () {
    yield* $.onAction('syncFromZustand').run(() =>
      Effect.sync(() => {
        const zustandState = useOldStore.getState()
        return $.state.update((s) => ({ ...s, legacy: zustandState }))
      }),
    )
  }),
)

Main benefits

After migrating you get:

CapabilityZustandLogix
Async race controlmanualbuilt-in runLatest/runExhaust
Cancel requestsmanual AbortControllerhandled via Effect semantics
Type safetymanualSchema-driven inference
Debug toolingdevtools middleware neededbuilt-in DevTools
Cross-module communicationmanual DI$.use / Link.make

FAQ

Q: Do we need to migrate everything at once?

No. Keep Zustand and Logix running in parallel and migrate modules one by one.

Q: Will performance be worse?

Logix uses SubscriptionRef for fine-grained subscriptions; performance is comparable to Zustand.

Q: Is the learning curve steep?

The basics (Module + Logic + useModule) are similar to Zustand. Learn advanced Effect capabilities only when needed.

Next

On this page