Skip to content

ModuleProvider

Opens a scoped module around its children: it constructs the module, initializes it, and owns its mount, unmount and destroy. What a scope is and where one belongs is on Modules; this page is the API surface.

Signature

ts
ModuleProvider(props: ModuleProviderProps): JSX.Element

type ModuleProviderProps = ModuleParams & {
    deps?: unknown[]
    children?: ReactNode
}

type ModuleParams = {
    id?: string
    providers?: readonly ProviderInput[]

    onModuleInit?: ModuleHook
    onModuleMount?: ModuleHook
    onModuleUnmount?: ModuleHook
    onModuleDestroy?: ModuleHook
}
tsx
import { ModuleProvider } from "@remodulo/react"

function OrdersPage({ customerId }: { customerId: string }) {
    return (
        <ModuleProvider
            providers={[OrdersApi, OrdersStore]}
            deps={[customerId]}
            onModuleMount={(resolver) => resolver.resolve(OrdersStore).load(customerId)}
        >
            <OrdersView />
        </ModuleProvider>
    )
}

OrdersApi and OrdersStore live and die with this boundary. A new customerId changes deps, so the module is destroyed and rebuilt with fresh instances, and onModuleMount loads again.

id

Optional; defaults to a generated one. It is what ModuleTraversal's findAncestorById and findDescendantById look up, and what error messages name the module by.

providers

A ProviderInput is a provider or a feature. The array is read when the module is constructed and never again - a different array on a later render changes nothing until the module is rebuilt. There is no registration after construction.

The four module hooks

HookFiresTimes
onModuleInitduring init, before every provider'sonce
onModuleMountwhen the boundary mountsas many as React says
onModuleUnmountwhen the boundary unmounts, after every provider'sas many as React says
onModuleDestroywhen the module diesonce

Each hook is called with the module's own Resolver. ModuleHook is (resolver: Resolver) => unknown, so a hook reads through the module it belongs to without a component around it.

An inline arrow is fine. The provider always calls the latest render's function, and passing a new one does not rebuild the module. Only deps, a new parent, or an explicit rebuild do that.

deps

Follows React's hook-deps rule exactly: element-wise Object.is plus a length comparison, and undefined on either side never triggers. Anything that changes destroys the module and builds a new one, with new instances throughout. The comparison runs on a layout effect, so several render attempts with the same new dependency build one module.

A parent module is required

The module forks the nearest module above it. With none in context - no <AppProvider> at the root, no enclosing boundary - construction throws:

ModuleProvider requires a parent module in context. Wrap it in <AppProvider>, or nest it under another <ModuleProvider>.

A new parent means a new module. When the module in context changes identity, this provider builds a fresh module under it and the previous one is torn down.

Where to go next

Guides

Reference

MIT licensed.