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>;
}
HookReturns
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.