Logix

Composability Map

A default-path guide to composing @logixjs/core + @logixjs/react (Module / Logic / Runtime / React).

This page is not “yet another way to do things”. It’s a map of existing Logix composition points, answering the questions you’ll hit in real apps:

  • Should this be a singleton or multi-instance?
  • If a module composes child modules, how does the UI get the child within the parent instance scope?
  • Should cross-module collaboration live inside a module ($.use) or outside modules (Process.link / Link.make)?
  • When do you need subtree overrides like RuntimeProvider.layer?

One-sentence rules (prefer the default path)

  1. Make singleton vs multi-instance explicit: useModule(ModuleDef/ModuleTag) is a Provider-scoped singleton; useModule(Impl, { key }) is multi-instance.
  2. For parent-child composition, prefer imports (strict scope): children follow the parent instance; Logic uses $.use(Child), UI uses host.imports.get(Child.tag).
  3. For “runtime-owned” glue logic, prefer processes: when collaboration belongs to the runtime (not a single module), attach it via processes.
  4. UI should bind to a Host by default: don’t “climb the imports tree” everywhere; resolve once at the boundary and pass ModuleRef down.

Decision tree (Mermaid)

Composition cheat sheet (what should I use?)

What you’re solvingRecommended entryScope intuitionWhere it livesRead more
Local UI state (replace useState/useReducer)useLocalModulecomponent lifetimeReact componentReact integration
Page/session multi-instance (tabs/sessions)useModule(Impl, { key })one instance per (Impl, key)React componentReact integration
Parent-child composition (children follow parent)imports + $.use(Child)strict imports scopeModuleImpl / LogicCross-module communication
UI reads child within parent instance scopehost.imports.get(Child.tag) / useImportedModuleparent instance scopeReact componentuseImportedModule, Route-scope modals
Long-running cross-module collaboration (runtime glue)Process.link / Link.makeruntime processesRoot processesCross-module communication, Runtime
Fixed root singleton readLogix.Root.resolve(Tag)fixed root providerLogicCross-module communication
Subtree env override (light config)RuntimeProvider layer={...}subtree env overrideReact treeRuntime
Caller DX (author-side)handle extensionsextra handle fieldsmodule author codeModule handle extensions
Reuse “logic snippets / flow templates”Pattern functions(config) => Effect or ($, config) => ...guide/patterns / helpersPattern example

Common footguns (avoid these)

  1. Treating an imports child as a global singleton: writing useModule(Child) / useModule(Child.tag) when you actually need host.imports.get(Child.tag).
  2. Using Link/Process to “pick an instance”: processes are for singleton sets; for multi-instance collaboration, let the owner module drive it within its instance scope via $.use(...).
  3. Climbing the imports tree everywhere: host.imports.get(A).imports.get(B)... scatters dependencies; resolve once at the boundary and pass ModuleRef down.
  4. Expecting local overrides to affect root singletons: Root.resolve reads from the fixed root provider; if you need override semantics, use imports or RuntimeProvider.layer.

Next

On this page