lit-ui-router-mobx
v0.3.3
Published
MobX bindings for lit-ui-router: an observable router store and reaction-based Lit ReactiveControllers
Maintainers
Readme
lit-ui-router-mobx
MobX bindings for lit-ui-router: an observable router store and reaction-based Lit ReactiveControllers.
A thin wrapper on top of lit-ui-router — it registers no custom elements and adds no routing behavior. It mirrors the router's state into MobX observables and gives components a declarative, lifecycle-safe way to observe them (and any other MobX state) with automatic requestUpdate() — no manual refresh plumbing.
Features
RouterStore— an observable mirror of the current state, params, and transition; one store (and one transition hook) per router viaRouterStore.for(router)RouterReactionController— observes the store of the nearest<ui-router>context, discovered automatically through theui-router-contextevent; no prop drilling and no store wiring in router configurationReactionController— the generic primitive: runs a MobXreactionover an explicit selector while the host is connected; works with any MobX observables, not just the router- Lifecycle-safe — reactions are created in
hostConnectedand disposed inhostDisconnected; they fire immediately on (re)connect so components that re-enter the DOM (e.g. sticky states) never render stale values
Installation
npm install lit-ui-router-mobx mobx
# or
pnpm add lit-ui-router-mobx mobx
# or
yarn add lit-ui-router-mobx mobxlit-ui-router, lit, mobx, and @uirouter/core are peer dependencies.
Quick Start
import { html, LitElement } from 'lit';
import { RouterReactionController } from 'lit-ui-router-mobx';
class AppNav extends LitElement {
// Re-renders only when the section's visibility actually flips —
// not on every transition.
private active = new RouterReactionController(this, (route) => ({
inbox: route.includes('inbox.**'),
contacts: route.includes('contacts.**'),
}));
render() {
return html`...${this.active.value.inbox ? 'Inbox is open' : ''}...`;
}
}No router configuration is required: the controller discovers the router from the enclosing <ui-router> element on hostConnected, and RouterStore.for(router) lazily attaches the store's single transition hook on first use.
API
RouterStore
An observable mirror of a router's current state, updated by one transitionService.onSuccess hook.
| Member | Description |
| --------------------------- | ---------------------------------------------------------------------------- |
| RouterStore.for(router) | The store for a router — memoized, one per router instance |
| current | The current StateDeclaration (globals.current) |
| params | The current RawParams (globals.params), replaced per transition |
| transition | The most recent successful Transition |
| includes(stateOrName, p?) | Observable version of StateService.includes (supports globs like 'a.**') |
| attach(router) | Manual attachment, for self-managed store instances |
RouterReactionController
A ReactiveController that observes the RouterStore of the host's <ui-router> context:
new RouterReactionController(host, selector, options?)selector: (store: RouterStore) => T— the observed expression; the result is exposed as.valueoptions.router— explicit router instance, skipping context discoveryoptions.onChange— effect invoked when the selected value changes (and once on every (re)connect); useful for resetting component state from route paramsoptions.equals— MobX comparer (e.g.comparer.structural) for precise, value-based change detection
ReactionController
The generic primitive behind RouterReactionController — the same selector/options contract over any MobX observables:
import { comparer } from 'mobx';
import { ReactionController } from 'lit-ui-router-mobx';
class NavHeader extends LitElement {
private auth = new ReactionController(
this,
() => ({ user: SessionStore.user, loggedIn: SessionStore.loggedIn }),
{ equals: comparer.structural },
);
render() {
const { user, loggedIn } = this.auth.value;
// ...
}
}Why selectors instead of render auto-tracking?
Mixins like MobxLitElement auto-track every observable read in render(). The controllers here are the composition-friendly alternative:
- No base class required — controllers attach to any
LitElement(or anyReactiveControllerHost) - Dependencies are explicit: the selector names exactly which observables drive the host
equals: comparer.structuralavoids re-renders when a recomputed value is structurally unchanged- The reaction lifecycle is bound to the host's connection lifecycle automatically
