Skip to content

feat!: Legend state port.#132

Draft
stephenh wants to merge 1 commit intomainfrom
feat/legend-state
Draft

feat!: Legend state port.#132
stephenh wants to merge 1 commit intomainfrom
feat/legend-state

Conversation

@stephenh
Copy link
Contributor

@stephenh stephenh commented Mar 18, 2026

Migration Complete: MobX → Legend-State v3

Results: 167/167 tests passing, build clean, zero MobX references remaining.

What changed

Production code (7 files):

  • rules.ts Removed action() wrapper — plain function
  • utils.ts Removed isObservable/toJS; added isClassInstance for deepClone
  • setupTests.ts Removed configure({ enforceActions })
  • fields/fragmentField.ts observable({value:1}) → observablePrimitive(1), removed makeAutoObservable
  • fields/valueField.ts All mutable state (touched, _readOnly, _loading, _focused, rules) backed by Legend-State observables. Early return when setting same value.
  • Removed MobX class support.
  • fields/listField.ts Tick pattern migrated. add/remove/set/commitChanges wrapped in batch(). Child value tracking via getter chain (no observe propagation).
  • fields/objectField.ts Same pattern — tick + batch + getter-based child tracking. set/revertChanges/commitChanges batched. No makeAutoObservable.

Key architectural decisions:

  1. No makeAutoObservable replacement — getters work through Legend-State dependency tracking via .get() calls on observablePrimitive ticks
  2. Getter-based child propagation instead of observe() reactions — avoids infinite loops with observer HOC
  3. Explicit batch() for multi-mutation operations — MobX auto-batched, Legend-State doesn't
  4. Early return on same-value set — avoids unnecessary observer notifications

Tests (6 files):

  • autorun → observe (1-arg)
  • reaction → observe (2-arg, with e.num > 0 guard to skip first fire)
  • Observer render prop → observer HOC wrapping
  • Removed: MobX class tests, observable array wrapping test, mobx-behavior.test.ts

Known limitation

The observer HOC from @legendapp/state/react causes infinite re-render loops when used with useFormState (the form state hook modifies observables in useEffect that observer tracks). The FormStateApp demo component works without observer because useFormState already handles re-rendering. This needs investigation — likely a Legend-State v3 beta interaction with React's effect lifecycle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant