Context & hooks
View classes inject at construction, but plain function components — reusable UI pieces, third-party wrappers — participate in the same injector tree through InjectorContext and three hooks.
The plumbing
The framework's host wraps every mounted view in InjectorContext.Provider with that view's injector. Function components read it with hooks — they're ordinary Preact context hooks, so the rules of hooks apply (they're for function components only, never render()):
import { useService, useController, useInjector, Provide } from '@preact-mvc/core';
import { ThemeService } from './theme.service';
function ThemeBadge() {
const theme = useService(ThemeService); // inject() for function components
return <span class="badge">{theme.current.value}</span>;
}| Hook | Returns |
|---|---|
useInjector() | The nearest enclosing Injector |
useService(token) | injector.get(token) — same resolution as inject() |
useController(Cls) | A controller instance scoped to this component mount, with lifecycle — see below |
useController vs inject()
useController deliberately bypasses the declared-scope lookup: it always creates a fresh controller owned by the component instance, calling its onInit/onDestroy with the mount. Use it when a reusable piece wants its own controller regardless of what ancestors declared:
function ColorPicker() {
// Every mounted ColorPicker gets its own controller + lifecycle.
const c = useController(ColorPickerController);
return <input type="color" value={c.value.value}
onInput={(e) => (c.value.value = e.currentTarget.value)} />;
}<Provide> — ad-hoc scopes in JSX
Provide creates a child injector for its subtree — shadowing or adding providers without a module or component declaration:
function Sidebar() {
return (
<Provide providers={[SidebarStore, { provide: DENSE, useValue: true }]}>
{/* children resolve SidebarStore + DENSE from this scope */}
<NavList />
</Provide>
);
}Same hierarchy rules as everywhere: the Provide scope delegates upward, so children still see module and root services, and a local registration shadows an outer one for this subtree only.
When to use what
- Feature building blocks — function components reading services:
useService. - Reusable interactive widget — fresh controller per mount:
useController. - Local override islands — a themed region, an embedded sub-tool:
<Provide>. - Everything else — views with real dependency graphs: class views +
inject(). See Views.