@modular-react/react
v2.0.1
Published
React bindings for @modular-react/core: hooks, contexts, error boundaries, and scoped stores.
Readme
@modular-react/react
React bindings for the @modular-react/core package. Provides context providers, hooks, and components that the router-specific runtime packages use internally.
Installation
npm install @modular-react/reactWhat's included
- Shared dependencies:
SharedDependenciesContext,createSharedHooks(factory that returnsuseStore,useService,useReactiveService,useOptional) - Scoped stores:
createScopedStorewithuseScopedhook - Slots:
SlotsContext,useSlots,RecalculateSlotsContext,useRecalculateSlots,DynamicSlotsProvider,createSlotsSignal - Navigation:
NavigationContext,useNavigation - Modules:
ModulesContext,useModules,getModuleMeta - Error boundary:
ModuleErrorBoundary - Module-exit plumbing:
ModuleExitProvider,useModuleExit,useModuleExitDispatcher,ModuleEvent. The "step 0" pattern — a module entry fires an exit from outside any journey, the composition root decides what it means. - Standalone hosts:
ModuleRouterenders a module entry as a route element (router-mode step 0). Pairs withModuleTabfrom@modular-react/journeysfor the workspace-mode variant. - Lazy entry resolution:
resolveEntryComponent(entry)returns{ Component, preload }for either an eager ({ component }) or a lazy ({ lazy: () => import(…) })ModuleEntryPoint. Memoized per entry-object identity viaWeakMap. Used by bothJourneyOutletandModuleTabso the lazy wrapper / import promise is shared across renders, hot reloads, and StrictMode double-mount.preloadEntry(entry)is the convenience wrapper for hover-prefetch UIs and other manual warm-up paths. - Re-exported from
@modular-react/core: all types,createStore,isStore,isStoreApi,isReactiveService,separateDeps,defineModule,defineSlots, slot/navigation/validation functions, and runtime helpers
Usage
Most apps import hooks from a router-specific package (@react-router-modules/core or @tanstack-react-modules/core). The router-specific runtime packages re-export relevant items from this package.
import { useSlots, useNavigation, useModules } from "@modular-react/react";Router-mode "step 0" with ModuleRoute
import { ModuleExitProvider, ModuleRoute } from "@modular-react/react";
function LaunchPage() {
return <ModuleRoute module={launcherModule} entry="pickWorkflow" routeId="/launch" />;
}
// Composition root decides which exit becomes which action.
<ModuleExitProvider
onExit={(ev) => {
if (ev.exit === "startOnboarding") runtime.start(onboardingHandle, ev.output);
}}
>
<RouterProvider router={router} />
</ModuleExitProvider>;<JourneyProvider> from @modular-react/journeys composes over
ModuleExitProvider automatically — apps that use the journeys plugin
do not need to mount both.
Manual prefetch with preloadEntry
preloadEntry(entry) triggers a lazy entry's dynamic import without rendering the component. Call it from a hover handler, an analytics-driven prediction, or a useEffect that knows the user is about to advance:
import { preloadEntry } from "@modular-react/react";
import { billingModule } from "./billing-module.js";
function PlanCard({ onClick }: { onClick: () => void }) {
return (
<button onClick={onClick} onMouseEnter={() => preloadEntry(billingModule.entryPoints.collect)}>
Continue to billing
</button>
);
}Calls are idempotent — the underlying WeakMap cache returns the same in-flight or resolved promise across hover, click, and any <JourneyOutlet preload> that picked the same entry.
In tests, prefer preloadEntries(modules) from @modular-react/testing — it walks every lazy: entry on a module set in one call, so renders commit synchronously without a Suspense fallback flash. See @modular-react/testing.
See the main documentation for the full guide.
