Modules & State
Learn how to define state and actions in Logix.
In Logix, everything is a Module. A Module is the container for State, Actions, and Logic.
This guide walks you through building a simple counter module from scratch.
Who is this for?
- You’ve completed the counter example in “Quick Start” and want to understand Module design systematically.
- You’ve used Redux / Zustand and want to compare Logix’s approach.
Prerequisites
- Basic TypeScript syntax and types
- A rough understanding of “State + Action + Reducer”
What you’ll get
- Define clear State / Actions schemas for your own product
- Know what belongs in a Module’s
reducersvs in Logic watchers - Assemble a reusable module implementation (ModuleImpl) with
Module.make
1. Define State
First, define what the Module state looks like. Logix uses effect’s Schema to define a strongly typed state structure.
import { Schema } from 'effect'
// Define State Schema
const State = Schema.Struct({
count: Schema.Number,
isLoading: Schema.Boolean,
})2. Define Actions
Next, define what operations can happen to the state. Actions are also defined via Schema.
// Define Actions
const Actions = {
increment: Schema.Void, // no payload
decrement: Schema.Void, // no payload
setValue: Schema.Number, // payload is a number
}3. Create the Module blueprint
Now, combine State and Actions into a Module.
import * as Logix from '@logixjs/core'
export const CounterDef = Logix.Module.make('Counter', {
state: State,
actions: Actions,
})4. Implement Logic
After defining the “shape”, implement concrete business logic. A recommended style is for Module.logic to return an Effect.gen, and write all watchers inside the generator body:
import { Effect } from 'effect'
const CounterLogic = CounterDef.logic(($) =>
Effect.gen(function* () {
// Watch increment
yield* $.onAction('increment').runFork(
$.state.update((s) => ({ ...s, count: s.count + 1 })),
)
// Watch decrement
yield* $.onAction('decrement').runFork(
$.state.update((s) => ({ ...s, count: s.count - 1 })),
)
// Watch setValue
yield* $.onAction('setValue').runFork((value) =>
$.state.update((s) => ({ ...s, count: value })),
)
}),
)The example above updates state through watchers in Logic. For core state changes that are purely synchronous and have no side effects (like a counter), you can also declare Primary Reducers via the optional reducers field in Logix.Module. The Runtime applies them synchronously during dispatch. For the separation of responsibilities and a fuller example, see the “Thinking in Logix” page (Primary Reducer vs Watcher).
5. Assemble the Implementation
Finally, mount the logic onto the Module and provide initial state.
export const CounterModule = CounterDef.implement({
initial: { count: 0, isLoading: false },
logics: [CounterLogic],
})
export const CounterImpl = CounterModule.implNow CounterImpl can be used in React components.
function Counter() {
const counter = useModule(CounterModule)
// ...
}Next
Now that you know how to define state and simple synchronous logic, the next step is handling async flows and side effects. 👉 Flows & Effects