Skip to content

feat: add todo-mobx-app example with MobX↔FASTElement bridge#7523

Merged
janechu merged 3 commits into
mainfrom
users/janechu/create-app-using-mobx
May 21, 2026
Merged

feat: add todo-mobx-app example with MobX↔FASTElement bridge#7523
janechu merged 3 commits into
mainfrom
users/janechu/create-app-using-mobx

Conversation

@janechu
Copy link
Copy Markdown
Collaborator

@janechu janechu commented May 19, 2026

Pull Request

📖 Description

Adds a new example, examples/todo-mobx-app/, that demonstrates how to bridge a MobX state store into FASTElement template bindings.

The example introduces two thin, application-scoped decorators that collapse the usual MobX-in-a-web-component boilerplate (a manual reaction() per observed field + manual Observable.notify() + manual disposer wiring in disconnectedCallback) down to two annotations:

  • @mobxObservableProperty — marks a getter on a FASTElement subclass as one whose value comes from a MobX-tracked source. The getter is wrapped with Observable.track(this, key) so FAST treats it as a normal observable property in templates.
  • @mobxObserver — class decorator that, on connectedCallback, sets up one MobX reaction per @mobxObservableProperty key. When MobX detects a change, the reaction calls Observable.notify(this, key) and FAST re-renders the dependent template bindings. Reactions are disposed on disconnectedCallback.

The bridge is uni-directional (MobX → FAST). Two-way input bindings (e.g. <todo-form>'s draft text) still use plain FAST @observable because that state is UI-local and not part of the MobX store.

The example app itself is a small Todo application with the typical features (add / toggle / remove / toggle-all / filter / clear-completed / item count) plus localStorage persistence via a mobx.autorun. State lives in a singleton TodoStore (makeAutoObservable) and is shared across all components, so no props need to be passed down — components read directly from the store through @mobxObservableProperty getters.

A DESIGN.md documents the bridge architecture, lifecycle ordering, the rationale for recycle: false on the <todo-list> repeat directive (otherwise FAST would rebind existing rows to new Todo objects without tearing down their MobX reactions, leaving stale subscriptions), and known gotchas around volatile getters and useDefineForClassFields.

This is an additive example only — no existing packages or public API are modified.

👩‍💻 Reviewer Notes

  • The bridge code lives entirely under examples/todo-mobx-app/src/mobx-integration/. It is intentionally scoped to the example, not promoted to @microsoft/fast-element, so the team can evaluate the pattern before deciding whether to formalize it.
  • Suggested smoke test: npm start -w @microsoft/fast-todo-mobx-app-example (port 9001), then add several todos, toggle some, switch filters, remove an item that was previously in the middle of the list, then toggle a row that took its place — this exercises the recycle: false path that ensures per-item MobX reactions are torn down and rebuilt when rows shift.
  • examples/todo-mobx-app/tsconfig.json sets useDefineForClassFields: false and skipLibCheck: true. The first is required so MobX's makeAutoObservable(this) sees constructor-assigned fields (rather than Object.defineProperty-defined fields) and can convert them to observables. The second sidesteps a known gap in mobx@6.13.5's ObservableMap lib types vs. TypeScript's esnext Map interface.

📑 Test Plan

  • npm run build (full repo): passes; the new package builds in ~1s via tsgo -p tsconfig.json && vite build and emits a ~131 KB www/ bundle.
  • npm run biome:check on the new files: clean.
  • npm run checkchange: no change files required (the example package is "private": true, so beachball skips it).
  • Manual smoke test via Playwright against npm start -w @microsoft/fast-todo-mobx-app-example: add 3 todos through the form (verifies action propagation), verify list/stats text update reactively, toggle the middle row (verifies per-item bridge), switch the filter to "completed" (verifies computed view), remove an item and toggle the row that shifted into its slot (verifies recycle: false prevents stale reactions), reload the page (verifies localStorage persistence). All scenarios behaved as expected.
  • No new unit tests added — the example follows the convention of the existing examples/todo-app/, which also has no tests of its own.

✅ Checklist

General

  • I have included a change request file using $ npm run change
  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

⏭ Next Steps

If the team likes the pattern, a natural follow-up is to promote mobx-integration/ into its own optional adapter package (e.g. @microsoft/fast-element-mobx) so that other apps can consume the decorators without copy-pasting them.

janechu and others added 3 commits May 21, 2026 09:35
Adds examples/todo-mobx-app/, a small Todo app that demonstrates how to
bridge a MobX state store into FASTElement template bindings via two
example-scoped decorators:

- @mobxObservableProperty wraps a getter with Observable.track so FAST
  treats it as a normal observable property in templates.
- @mobxObserver, applied to the class, sets up one mobx.reaction per
  decorated getter on connectedCallback and disposes them on
  disconnectedCallback, calling Observable.notify when MobX detects a
  change.

The bridge is uni-directional (MobX -> FAST). UI-local state still uses
plain FAST @observable.

The example app uses a singleton TodoStore (makeAutoObservable) shared
across all components, plus localStorage persistence via mobx.autorun.

The <todo-list> repeat directive uses { recycle: false } so per-item
MobX reactions are disposed when rows are removed; without this, FAST
would rebind existing views to new Todos without tearing down their
reactions, leaving stale subscriptions.

A DESIGN.md documents the bridge architecture, lifecycle ordering, and
known gotchas (volatile getters, useDefineForClassFields, recycle).

No existing packages or public API are modified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…torun

Replaces the two custom decorators (@mobxObserver and @mobxObservableProperty)
plus the ~180-line src/mobx-integration/ folder with a stock pattern: each
component declares the slices of MobX state it renders as FAST @observable
fields and uses a single autorun in connectedCallback to mirror values from
the MobX store, disposing in disconnectedCallback.

Why:
- No new abstractions on top of FAST or MobX — readers need to know both
  libraries' standard APIs and nothing else.
- Bridge code shrinks from ~180 lines to 0.
- <todo-list> repeat directive no longer needs recycle: false: <todo-item>
  rebuilds its autorun via FAST's standard `todoChanged` callback when the
  bound Todo changes on view recycle.
- Calling autorun before super.connectedCallback() ensures the mirror fields
  have correct values when the template first binds, avoiding initial flicker.

README and DESIGN.md rewritten to document the new pattern and explain why
no custom mechanism is needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…m tokens in todo-mobx-app

- Move examples/todo-app/ to examples/csr/todo-app/ (history preserved via git mv).
- Move examples/todo-mobx-app/ to examples/csr/todo-mobx-app/ (history preserved).
- Group example apps by rendering strategy; the design-system package stays at
  examples/design-system/ since it is shared across strategies.
- Add 'examples/csr/*' to the root package.json workspaces glob.
- Wire @microsoft/fast-examples-design-system into todo-mobx-app: import
  tokens.css once from src/main.ts, declare the module shim, and force
  data-theme='light' on <html>.
- Rewrite every todo-mobx-app *.styles.ts to consume --fast-* tokens
  (backgrounds, foregrounds, padding, gap, corners, strokes, shadows, font
  metrics, durations) instead of hard-coded values; accent-color on the
  native checkbox now resolves to the brand token too.
- Update README/DESIGN docs for both apps and the workspace-level
  examples/README.md and examples/DESIGN.md to reflect the csr/ layout and
  the design-system integration.
- Update .github/copilot-instructions.md example-project entries to the new
  paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu force-pushed the users/janechu/create-app-using-mobx branch from f4c3162 to 8d83d7b Compare May 21, 2026 16:42
@janechu janechu marked this pull request as ready for review May 21, 2026 17:28
@janechu janechu merged commit b5af8e1 into main May 21, 2026
17 checks passed
@janechu janechu deleted the users/janechu/create-app-using-mobx branch May 21, 2026 18:03
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.

2 participants