@pastweb/react
v2.2.1
Published
React bindings for Pastweb: progressive framework utilities for routing, portals, API query hooks, SSR entries, slots, and reactive state.
Maintainers
Readme
@pastweb/react
React integration package for building tools-powered React applications.
It provides React-specific bindings for the framework-agnostic primitives in @pastweb/tools: global context providers, API/query hooks, async component rendering, router integration, entry composition, islands, portals, and React utility hooks. Use it as a standalone React utility layer or as part of a larger Pastweb-based stack.
Features
- Tools-first architecture — React components and hooks wrap the core
@pastweb/toolsfunctionality instead of replacing it. - Reactive Global Context —
GlobalContext, installers,getContext, andsetContextbridge the tools global context into React trees. - API/query integration — React wrappers for query cache access,
useQuery,useMutation,useQueries,useInfiniteQuery, and reusable query state. - SSR-ready composition — Entry helpers,
AsyncComponent, andIslandsupport the page-router SSR and partial hydration flow. - Router integration — React hooks and components for
createViewRouter. - Portal support — React providers and hooks for portal descriptors and anchors.
- TypeScript-first — Public APIs include TSDoc and exported types for app-level integration.
Installation
npm i -S @pastweb/react
# or
pnpm i -S @pastweb/react
# or
yarn add -S @pastweb/reactDocumentation Overview
The documentation is organized into the following major categories. Each section provides syntax notes, practical examples, and integration guidance where useful.
- Core functions —
GlobalContext, installers, and context hooks for sharing tools-powered services in React. - API functions — Query cache provider, query cache installer, and React wrappers around tools API hooks.
- Async functions — Async Redux store helpers and color-scheme hooks.
- Routing — React integration for
createViewRouter. - Browser functions — Browser-aware hooks such as device matching.
- Element functions — Async components, entry rendering, islands, portals, and UI composition helpers.
- Hook functions — General React lifecycle and mediator hooks.
- Utility functions — Small React utilities such as
RenderandsetRef.
This project is distributed under the MIT licence.
Summary
- Core functions
- API functions
- Async functions
- Routing
- Browser functions
- Element functions
- Hook functions
- Utility functions
Core functions
GlobalContext
GlobalContext provides the reactive global context from @pastweb/tools to a React subtree. It can receive installer functions through use, and provider-scoped values through update.
Use installers for shared app services that should be available to descendants, such as routers, portal caches, or API query caches.
Example:
import { GlobalContext } from '@pastweb/react';
const installSession = () => ({
session: {
userId: 'user-1',
},
});
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<GlobalContext use={installSession}>
{children}
</GlobalContext>
);
}Installers receive the keys already present in the local context. This is useful for defensive installers that need to avoid duplicate setup:
Example:
const installTheme = keys => {
if (keys.includes('theme')) return {};
return {
theme: 'dark',
};
};Nested providers inherit parent values and can add or override values with update:
Example:
<GlobalContext update={{ appName: 'Docs' }}>
<GlobalContext update={{ routeDepth: 1 }}>
<Page />
</GlobalContext>
</GlobalContext>If an installer returns a key that already exists in the local context, GlobalContext throws. This catches accidental double installation early.
getContext
Reads one context key and re-renders when that key changes.
Example:
import { getContext } from '@pastweb/react';
function UserName() {
const user = getContext<{ name: string }>('user');
return <span>{user.name}</span>;
}setContext
Writes one value into the active context. Components reading the same key with getContext will update.
setContext reads React context internally, so call it during a component or custom hook render path.
Example:
import { setContext } from '@pastweb/react';
function StatusWriter({ ready }: { ready: boolean }) {
setContext('status', ready ? 'ready' : 'idle');
return null;
}API functions
The API helpers are React wrappers around the framework-agnostic query/cache primitives from @pastweb/tools. The cache and agent still live in tools; this package only provides React context access and React render updates for the reactive query states.
ApiQueryProvider
Provides a QueryCache to a React subtree without using GlobalContext.
Example:
import { createQueryCache } from '@pastweb/tools';
import { ApiQueryProvider } from '@pastweb/react';
const queryCache = createQueryCache();
<ApiQueryProvider queryCache={queryCache}>
<App />
</ApiQueryProvider>installApiCache
Installs a QueryCache into GlobalContext, which can then be read with useApiQueryCache.
Example:
import { createQueryCache } from '@pastweb/tools';
import { GlobalContext, installApiCache } from '@pastweb/react';
const queryCache = createQueryCache();
<GlobalContext use={installApiCache({ queryCache })}>
<App />
</GlobalContext>reuseQuery
Bridges any tools reactive query-like state into React rendering. The state is created once, and React re-renders when the enumerable fields from the first created state change.
Syntax
function reuseQuery<TState extends Record<PropertyKey, any>>(
createState: () => TState,
): TStateExample:
import { createApiAgent, createQueryCache } from '@pastweb/tools';
import { useQuery as createToolsQuery } from '@pastweb/tools';
import { reuseQuery } from '@pastweb/react';
const queryCache = createQueryCache();
const agent = createApiAgent({ queryCache });
function useUsers() {
return reuseQuery(() => createToolsQuery({
fn: () => agent.get('/api/users', { queryKey: ['users'] }),
}));
}reuseMutation
Alias of reuseQuery for mutation-style state. It uses the same implementation, but makes custom mutation hooks easier to read.
Syntax
function reuseMutation<TState extends Record<PropertyKey, any>>(
createState: () => TState,
): TStateExample:
import { useMutation as createToolsMutation } from '@pastweb/tools';
import { reuseMutation } from '@pastweb/react';
function useSaveUser() {
return reuseMutation(() => createToolsMutation({
fn: user => agent.post('/api/users', user),
}));
}useQuery
React wrapper for @pastweb/tools useQuery.
Syntax
function useQuery<T>(config: QueryConfig<T>): QueryInfo<T>Example:
function Users() {
const users = useQuery({
fn: () => agent.get('/api/users', { queryKey: ['users'] }),
});
if (users.isLoading) return <span>Loading...</span>;
if (users.isError) return <span>Could not load users</span>;
return (
<ul>
{(users.data ?? []).map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}useMutation
React wrapper for @pastweb/tools useMutation.
Syntax
function useMutation<T>(config: MutationConfig<T>): MutationInfo<T>Example:
const saveUser = useMutation({
fn: payload => agent.post('/api/users', payload),
});
<button disabled={saveUser.isMutating} onClick={() => saveUser.mutate(user)}>
Save
</button>useQueries
React wrapper for multiple tools queries.
Syntax
function useQueries<T extends readonly QueryConfig<any>[]>(
config: UseQueriesInput<T>,
): UseQueriesInfo<T>Example:
const dashboard = useQueries({
queries: [
{ fn: () => agent.get('/api/users', { queryKey: ['users'] }) },
{ fn: () => agent.get('/api/posts', { queryKey: ['posts'] }) },
],
});useInfiniteQuery
React wrapper for paginated tools queries.
Syntax
function useInfiniteQuery<TPage, TPageParam = unknown>(
config: InfiniteQueryConfig<TPage, TPageParam>,
): InfiniteQueryInfo<TPage, TPageParam>Example:
const posts = useInfiniteQuery({
initialPageParam: 1,
fn: page => agent.get(`/api/posts?page=${page}`, {
queryKey: ['posts', page],
}),
});
<button disabled={!posts.hasNextPage} onClick={posts.fetchNextPage}>
More
</button>Async functions
createReduxAsyncStore
Generally there is not any need to initialise redux asyncronusly, even if you are using a micro-frontends loader helper as Module Federation or Native Federation cares about it, anyway there are other cases where this functionality is a need, as example if you need to initialise the redux store getting data from indexedDB.
The createReduxAsyncStore function sets up and configures an asynchronous Redux store that supports dynamically adding and removing reducers after the store has been created. It also provides hooks for dispatching actions and selecting state within a TypeScript-safe environment.
Syntax
function createReduxAsyncStore(options: ReduxStoreOptions): ReduxAsyncStore;Parameters
options:ReduxStoreOptions- An object containing configuration options for the Redux store.
settings:object- The settings object used to configure the Redux store, passed directly to configureStore from Redux Toolkit.
onInit:(store: Store) => Promise<void> | void- An optional initialization function that is executed when the store is initialized. This can be used for custom setup logic, such as loading initial data or middleware.
- An object containing configuration options for the Redux store.
Returns
ReduxAsyncStore: An object that represents the custom Redux store with enhanced functionality. It includes:store: The underlying Redux store, with added support for asynchronous reducers.asyncReducers: An object that stores the dynamically added reducers, keyed by their names.useDispatch: A typed hook for dispatching actions in a type-safe manner.useSelector: A typed hook for selecting state from the store, ensuring type safety.init(): A method that initializes the store and executes the onInit function.- Initializes the store and executes the optional onInit function provided in the options.
addReducer(reducerKey: string, reducer: Reducer): A method for dynamically adding a new reducer to the store.- Adds a new reducer to the store, keyed by
reducerKey. If a reducer with the same key already exists, it won't be added again.
- Adds a new reducer to the store, keyed by
removeReducer(reducerKey: string): A method for dynamically removing an existing reducer from the store.- Removes an existing reducer from the store based on the provided
reducerKey. If the reducer does not exist, the store remains unchanged.
- Removes an existing reducer from the store based on the provided
Example:
$ npm i -S @reduxjs/toolkitExample:
// @/react/redux/index.ts
import { createReduxAsyncStore } from '@pastweb/react/createReduxAsyncStore';
export const redux = createReduxAsyncStore({
settings: {
reducer: { /* initial reducers */ },
},
onInit: async (store) => {
// Custom initialization logic, e.g., loading initial data
},
});
// Dynamically adding a reducer
redux.addReducer('newFeature', newFeatureReducer);
// Dynamically removing a reducer
redux.removeReducer('oldFeature');
// Using typed hooks in your components
export const injectReducer = redux.addReducer;
export const useSelector = redux.useSelector;
export const useDispatch = redux.useDispatch;Then in your Providers component you can use the ReduxProvider for the asyncReduxStore:
Example:
// @/react/getEntry/Providers.tsx
import type { ReactNode } from 'react';
import { ReduxProvider } from '@pastweb/react';
import { redux } from '@/react/redux';
interface ProvidersProps {
children: ReactNode;
}
export default function Providers({ children }: ProvidersProps) {
return (
<ReduxProvider reduxStore={redux} fallback={<div>Loading...</div>}>
{ children }
</ReduxProvider>
);
}ReduxProvider
The ReduxProvider is a React component that ensures your application is wrapped with the Redux Provider only when the asynchronous Redux store is fully initialized.
It provides a convenient way to handle the async setup of a Redux store and offers an optional fallback UI to be displayed while the store is preparing as in the example above.
Props
reduxStore:ReduxStore- The Redux store object, which includes the
isReadypromise and store itself.
- The Redux store object, which includes the
children:React.ReactNode- The React elements (usually components) to be rendered inside the Redux
Provideronce the store is ready.
- The React elements (usually components) to be rendered inside the Redux
fallback:React.ReactElement(optional)- An optional React element to be displayed as a fallback UI while the Redux store is initializing.
useColorScheme
React hook for managing and tracking color scheme changes.
This hook mirrors useColorScheme from @pastweb/tools: pass options to create an internal MatchScheme, or pass a pre-created MatchScheme as the second argument. It listens for changes in the system's preferred color scheme and user-selected mode, then re-renders the React component.
Syntax
function useColorScheme(
options?: SchemeOptions,
matchScheme?: MatchScheme,
): [ColorSchemeInfo, (mode: string) => void]Parameters
options(SchemeOptions)
Options passed tocreateMatchSchemewhenmatchSchemeis not provided.matchScheme(MatchScheme, optional)
A pre-createdMatchSchemeinstance.
Returns
A tuple containing:
ColorSchemeInfo
The current color scheme information.(mode: string) => void
A function to update the color mode.
Example:
import { useColorScheme } from '@pastweb/react';
const [info, setMode] = useColorScheme({ defaultMode: 'auto' });
console.log(info.selected); // Outputs the currently selected color scheme
setMode('dark'); // Updates the mode to 'dark'Routing
ViewRouter
ViewRouter comes from @pastweb/tools. The React package can install that router with GlobalContext, or provide it locally with RouterProvider, then expose components/hooks that render route views, links, and reactive router state.
router setup
Create the router with createViewRouter, passing the React RouterView component as the view renderer. Install the router with installRouter and GlobalContext.
Example:
// @/lib/router.ts
import { createViewRouter, type Route } from '@pastweb/tools';
import { RouterView } from '@pastweb/react';
import { routes } from '@/views';
export const router = createViewRouter({
routes: routes as Route[],
RouterView,
});Example:
// @/AppProviders.tsx
import type { ReactNode } from 'react';
import { GlobalContext, installRouter } from '@pastweb/react';
import { router } from '@/lib/router';
const installRouterContext = installRouter({ router });
export function AppProviders({ children }: { children: ReactNode }) {
return (
<GlobalContext use={installRouterContext}>
{children}
</GlobalContext>
);
}For a local provider style, use RouterProvider directly:
Example:
import type { ReactNode } from 'react';
import { RouterProvider } from '@pastweb/react';
import { router } from '@/lib/router';
export function AppProviders({ children }: { children: ReactNode }) {
return (
<RouterProvider router={router}>
{children}
</RouterProvider>
);
}installRouter
installRouter creates a GlobalContext installer for the tools router. It stores the router under the tools router context key and initializes route depth to -1, so the first RouterView renders the root selected route.
Example:
import { installRouter } from '@pastweb/react';
const installRouterContext = installRouter({
router,
base: '/app',
});RouterProvider
RouterProvider provides a tools ViewRouter through dedicated React context. Use it when a subtree needs router hooks/components but you do not want to install the router through GlobalContext.
Example:
import { RouterProvider, RouterView } from '@pastweb/react';
<RouterProvider router={router} base="/app">
<RouterView />
</RouterProvider>RouterView
RouterView renders the selected route view for the current route depth. Nested RouterView components increment the depth through the nearest router provider context, while keeping compatibility with GlobalContext.
Props
- name:
string- Named view to render from
SelectedRoute.views. Defaults todefault.
- Named view to render from
- beforeShow:
(route: SelectedRoute) => SelectedRoute- Optional transform before the view is selected.
- rest:
Record<string, any>- Extra props forwarded to the selected view component.
Example:
import { RouterView } from '@pastweb/react';
export function Views() {
return <RouterView />;
}Example:
import { RouterView } from '@pastweb/react';
export function Views() {
const beforeShow = (route) => {
if (route.meta.requiresAuth && !isLoggedIn()) {
return { ...route, views: { default: LoginView } };
}
return route;
};
return <RouterView beforeShow={beforeShow} />;
}RouterLink
RouterLink renders an anchor whose href, active state, and click navigation come from router.getRouterLink.
Props
- path:
string- The target path for the link.
- params:
Record<string, string | number | boolean | null | undefined>- Optional route parameters interpolated into the path.
- searchParams:
URLSearchParams- Optional query parameters.
- hash:
string- Optional hash without the leading
#.
- Optional hash without the leading
- className:
string- Optional anchor class.
- preventNavigate:
boolean- Prevents click navigation while keeping the generated
href.
- Prevents click navigation while keeping the generated
- children:
ReactNode- Anchor content.
Example:
<RouterLink
path="/users/:id"
params={{ id: 123 }}
searchParams={new URLSearchParams({ tab: 'profile' })}
hash="details"
>
User profile
</RouterLink>useLocation
useLocation returns the current router location and re-renders when it changes.
Syntax
function useLocation(): LocationExample:
const location = useLocation();
return <span>{location.pathname}</span>;useNavigate
useNavigate returns the router navigation function.
Syntax
function useNavigate(): (path: string, state?: any) => Promise<void>Example:
const navigate = useNavigate();
await navigate('/dashboard', { from: 'login' });usePaths
usePaths returns the router paths filtered by the tools filterRoutes helper.
Syntax
function usePaths(filter?: FilterDescriptor): Route[]Example:
const adminPaths = usePaths({ role: 'admin' });useRoute
useRoute returns the selected route at the current RouterView depth.
Syntax
function useRoute(): SelectedRouteExample:
const currentRoute = useRoute();
return <h1>{currentRoute.path}</h1>;useRouteDepth
useRouteDepth returns the current router view depth. The installed root value is -1.
Syntax
function useRouteDepth(): numberExample:
const depth = useRouteDepth();useRouter
useRouter returns the installed tools ViewRouter.
Syntax
function useRouter(): ViewRouterExample:
const router = useRouter();
await router.navigate('/home');useRouterLink
useRouterLink returns the descriptor from router.getRouterLink and re-renders when the active route changes.
Syntax
function useRouterLink(props: RouterLinkOptions): RouterLinkExample:
const link = useRouterLink({
path: '/about',
});
<a href={link.pathname} onClick={() => link.navigate()}>
Go to About
</a>useSearchParams
useSearchParams returns the current URLSearchParams and the router setter.
Syntax
function useSearchParams(): {
params: URLSearchParams;
setSearchParams: (searchParams: URLSearchParams) => void;
}Example:
const { params, setSearchParams } = useSearchParams();
const next = new URLSearchParams(params);
next.set('page', '2');
setSearchParams(next);Browser functions
useMatchDevice
useMatchDevice tracks device matches with the tools createMatchDevice helper.
Syntax
function useMatchDevice(config: DevicesConfig): DevicesResultExample:
import { useMatchDevice, useMounted } from '@pastweb/react';
const config = {
phone: { mediaQuery: '(max-width: 320px)' },
tablet: { mediaQuery: '(max-width: 600px)' },
desktop: { mediaQuery: '(max-width: 1024px)' },
};
const { devices, onMatch } = useMatchDevice(config);
useMounted(() => {
onMatch('phone', (matches, device) => {
console.log(`${device}: ${matches}`);
});
});
return devices.phone ? <MobileNav /> : <DesktopNav />;reuseMatchDevice
reuseMatchDevice bridges a reactive device state created by the tools useMatchDevice helper into React rendering.
Syntax
function reuseMatchDevice(matchDevice: DevicesResult): DevicesResultExample:
import { useMatchDevice as createToolsMatchDevice } from '@pastweb/tools';
import { reuseMatchDevice } from '@pastweb/react';
const deviceState = createToolsMatchDevice({
phone: { mediaQuery: '(max-width: 640px)' },
});
function Navigation() {
const { devices } = reuseMatchDevice(deviceState);
return devices.phone ? <MobileNav /> : <DesktopNav />;
}Element functions
AsyncComponent
AsyncComponent loads a React component with the import() function and can wait for optional dependencies before rendering. Use it for route-level code splitting, feature modules, or setup work such as loading reducers before the component appears.
Syntax
function AsyncComponent(props: AsyncComponentProps): ReactElement | nullProps
component:() => Promise<ComponentModule>- The function that returns the component module promise.
dependencies:(Dependency | DependencyInfo)[](optional)- Dependencies to load before rendering the component.
fallback:ReactElement | null(optional)- The element rendered while the component and dependencies are loading.
Example:
import { AsyncComponent } from '@pastweb/react';
import { injectReducer } from '@/react/redux';
export function ProductRoute() {
return (
<AsyncComponent
component={() => import('@/react/components/ProductDetails')}
dependencies={[
{
dependency: () => import('@/react/redux/productReducer'),
exportName: 'default',
onSuccess: reducer => {
injectReducer('product', reducer);
},
},
]}
fallback={<div>Loading...</div>}
/>
);
}createEntry
The createEntry function simplifies the process of setting up, rendering, and unmounting a React component in a web application. It includes support for asynchronous dependencies and customizable providers.
Parameters
- options:
ReactEntryOptionsAn object that defines the configuration for the React entry. This includes options for providers, initial data, fallback components, and asynchronous dependencies.
Properties of options:
- Providers:
React.ComponentType | React.Fragment
- A wrapper component or React.Fragment to provide context or functionality to the entry component.
- Default:
Fragment.
- initData:
{ [key: string]: any }
- Initial data passed as props to the entry component.
- waitFor:
Array<Promise<any>>
- A list of promises to resolve before rendering the entry component. This allows for data fetching or other asynchronous operations to complete before display.
- fallback:
React.ReactNode
- A component or element to render while waiting for the promises in waitFor to resolve.
- Default:
Fragment.
Returns
A ReactEntry object containing methods for managing the lifecycle of the React component.
For more info about the Entry Object click here.
Example:
// @/react/getEntry/getEntry.ts
import { createEntry } from '@pastweb/react';
import { redux } from '@/react/redux';
import { Providers } from './Providers';
type EntryParams = {
entry?: Component | ReactElement;
props?: Record<string, any>;
entryElement?: HTMLElement;
};
export function getEntry(params: EntryParams = {}): ReactEntry {
const { entry, entryElement, props = {} } = params;
return createEntry({
entryElement,
EntryComponent: entry,
Providers,
initData: props,
waitFor: [ redux ],
fallback: <LoadingSpinner />,
});
}
// @/main.tsx
import { getEntry } from '@/react/getEntry';
import { App } fgrom './App';
const entry = getEntry({
entry: App,
entryElement: document.querySelector('#root') as HTMLElement,
});
// Mount the component
entry.mount();
// Later, unmount it
entry.unmount();EntryAdapter
EntryAdapter renders an entry inside a React tree. On the client it mounts the entry returned by createEntry; on the server it can load a separate ssrEntry through the SSR async task queue, keeping the server renderer out of the client bundle.
If your renderer uses the same entry implementation on client and server, omit ssrEntry; EntryAdapter will use entry during SSR too.
When using Vite, guard the async server import with import.meta.env.SSR so the client build can tree-shake the server-only module:
Example:
import { createEntry, EntryAdapter } from '@pastweb/react';
import { ProfileCard } from './ProfileCard';
function createProfileEntry() {
return createEntry({
EntryComponent: ProfileCard,
});
}
export function ProfileSlot(props: { userId: string }) {
return (
<EntryAdapter
entry={createProfileEntry}
{...(import.meta.env.SSR
? {
ssrEntry: () =>
import('./profile.server-entry').then(module => module.createProfileServerEntry()),
}
: {})}
userId={props.userId}
/>
);
}EntryAdapter also reads the nearest Island context. When rendered inside an Island, the nested entry hydrates existing server markup; outside an island it mounts normally.
Island
Island marks a server-rendered subtree that can hydrate with a client strategy such as load, idle, visible, media, or none.
The component does not accept provider props and does not install API, router, portal, or cache context. When an island needs those providers, extend the component rendered inside the island and include the providers there.
Island also provides an internal island context. Nested EntryAdapter components read that context and hydrate their entries automatically only when they are rendered inside an island.
Example:
function ProductIsland() {
return (
<AppProviders>
<ProductCard />
</AppProviders>
);
}
<Island client="visible" islandId="product-card">
<ProductIsland />
</Island>portals
React already supports portals. These helpers add a small framework wrapper around the @pastweb/tools portal primitives so applications can share the same portal model across packages.
portals setup
Portal entries render components into DOM elements called anchors. Create the anchor ids with generateAnchors, then either install the portal descriptor with installPortals and GlobalContext, or provide it locally with PortalsProvider.
Example:
// @/lib/portals.ts
import { Fragment, type ReactNode } from 'react';
import { generateAnchors } from '@pastweb/tools';
import { GlobalContext, createEntry, installPortals } from '@pastweb/react';
export const portalAnchors = generateAnchors([
'modal',
'toast',
]);
const installPortalContext = installPortals({
anchorsIds: portalAnchors,
getEntry: (props, component) => createEntry({
EntryComponent: component ?? Fragment,
initData: props,
}),
});
export function AppProviders({ children }: { children: ReactNode }) {
return (
<GlobalContext use={installPortalContext}>
{children}
</GlobalContext>
);
}The generated ids must be rendered as DOM anchors somewhere in the app shell:
Example:
import { portalAnchors } from '@/lib/portals';
export function PortalAnchors() {
return (
<>
<div id={portalAnchors.modal} />
<div id={portalAnchors.toast} />
</>
);
}getEntry is the customization point for portal rendering. Wrap the returned entry there when a portal needs providers, layout, or framework-specific mounting behavior.
PortalsProvider
PortalsProvider provides portal helpers through dedicated React context. Use it when a subtree needs portals but you do not want to install them through GlobalContext.
Example:
import { PortalsProvider } from '@pastweb/react';
import { portalAnchors } from '@/lib/portals';
<PortalsProvider anchorsIds={portalAnchors} getEntry={getEntry}>
<App />
</PortalsProvider>usePortalAnchors
The usePortalAnchors hook reads the installed portal anchor ids.
Syntax
function usePortalAnchors<T>(): TExample:
import { usePortalAnchors } from '@pastweb/react';
function PortalTargets() {
const anchors = usePortalAnchors<{ modal: string; toast: string }>();
return (
<>
<div id={anchors.modal} />
<div id={anchors.toast} />
</>
);
}usePortals
The usePortals hook reads the installed portal descriptor. Use a local type to keep portal paths typed in application code.
Syntax
function usePortals<T>(): TExample:
import { usePortals, type PortalFunction } from '@pastweb/react';
type AppPortals = {
modal: PortalFunction;
};
function OpenDirectly() {
const portals = usePortals<AppPortals>();
const openModal = () => portals.modal(<Dialog />).open();
return <button onClick={openModal}>Open Modal</button>;
}usePortal
The usePortal hook creates the handler consumed by the Portal component. The handler exposes open, update, close, and remove.
Calls made before the Portal component finishes wiring itself are replayed once the component is ready.
Syntax
function usePortal(): PortalHandler & { isReady: () => void }Portal
The Portal component binds a usePortal handler to a portal path. It renders null directly; the child element is mounted by the portal entry when the handler opens.
Props
- path:
string- Dot-separated path used to select the portal function from the descriptor.
- use:
PortalHandler- Handler returned by
usePortal.
- Handler returned by
- children:
ReactElement- Element rendered by the portal entry.
Example:
import { Portal, usePortal } from '@pastweb/react';
function ProductModalButton() {
const modal = usePortal();
return (
<>
<button onClick={() => modal.open()}>Open details</button>
<button onClick={() => modal.update({ variant: 'compact' })}>Compact</button>
<button onClick={() => modal.close()}>Close</button>
<Portal path="modal" use={modal}>
<ProductDetails variant="full" />
</Portal>
</>
);
}Slots
Slots is a boundary component for nested slot-aware components.
When useSlots scans children, it treats nested Slots boundaries as opaque children and does not collect Template elements inside them. This keeps parent and child slot collections independent while preserving the rendered child tree.
Parameters
children(ReactNode)
The nested slot-aware subtree.
Returns
The component renders its children unchanged.
Example:
import { Slots, Template, useSlots } from '@pastweb/react';
function Panel({ children }: { children: React.ReactNode }) {
const { Slot } = useSlots(children);
return (
<section>
<header>
<Slot name="title" />
</header>
<main>
<Slot />
</main>
</section>
);
}
<Panel>
<Template name="title">Settings</Template>
<Slots>
<Template name="title">Nested title</Template>
</Slots>
<p>Panel body</p>
</Panel>Nested slots
Wrap a nested slot-aware component with Slots when it appears inside another slot-aware component. The wrapper tells the parent collector to leave the child component's templates untouched so the child can collect them itself.
Example:
import { Slots, Template, useSlots } from '@pastweb/react';
function Page({ children }: { children: React.ReactNode }) {
const { Slot } = useSlots(children);
return (
<main>
<h1>
<Slot name="title" />
</h1>
<Slot />
</main>
);
}
function Card({ children }: { children: React.ReactNode }) {
const { Slot } = useSlots(children);
return (
<article>
<h2>
<Slot name="title" />
</h2>
<div>
<Slot />
</div>
<footer>
<Slot name="action" />
</footer>
</article>
);
}
<Page>
<Template name="title">Dashboard</Template>
<Slots>
<Card>
<Template name="title">Revenue</Template>
<p>Quarterly report</p>
<Template name="action">
<button>Open report</button>
</Template>
</Card>
</Slots>
</Page>useSlots
useSlots collects default children and named Template children into renderable slots.
The returned Slot component renders the default slot when no name is provided, renders fallback children when a named slot is missing, supports prop injection for element/function slot content, and supports custom mapping.
Syntax
function useSlots(defaultNodes: ReactNode): {
Slots: typeof Slots;
Slot: (props: SlotProps) => ReactNode;
}Parameters
defaultNodes(ReactNode)
Children to scan for default content andTemplatemarkers.
Returns
An object containing:
Slots(FunctionComponent<SlotsProps>)
Boundary component for nested slot-aware components.Slot(FunctionComponent<SlotProps>)
Renderer for default or named slot content.
Example:
import { Template, useSlots } from '@pastweb/react';
function Toolbar({ children }: { children: React.ReactNode }) {
const { Slot } = useSlots(children);
return (
<Slot
name="action"
props={{ label: 'Save' }}
map={(child, index) => (
<span key={index} className="toolbar-action">
{child}
</span>
)}
/>
);
}
<Toolbar>
<Template name="action">
{({ label }) => <button>{label}</button>}
</Template>
</Toolbar>Template
Template marks content that should be collected by useSlots.
Omitting name makes the template content part of the default slot. Template.reduce and Template.only are available for filtering child collections while preserving the same public API.
Parameters
name(string, optional)
Name of the slot to register. Defaults to the default slot.children(ReactNode | SlotFunction, optional)
Static content, React elements, arrays, or a function that receives slot props.
Returns
Template renders null; its content is consumed by useSlots.
Static methods
Template.reduce(children, fn)
Maps children while excludingSlotandTemplatemarker elements.Template.only(children)
Returns onlyTemplateelements from a child collection.
Example:
import { Template, useSlots } from '@pastweb/react';
function ActionPanel({ children }: { children: React.ReactNode }) {
const { Slot } = useSlots(children);
return <Slot name="action" props={{ label: 'Save changes' }} />;
}
<ActionPanel>
<Template name="action">
{({ label }) => <button>{label}</button>}
</Template>
</ActionPanel>Hook functions
useBeforeMount
useBeforeMount runs a function once during the component's first render. Use it for synchronous setup that must be available before the initial React commit.
Syntax
function useBeforeMount(fn: () => void): voidExample:
import { useBeforeMount } from '@pastweb/react';
function ExampleComponent() {
useBeforeMount(() => {
registry.register('example');
});
return <div>Hello, world!</div>;
}useBeforeUnmount
useBeforeUnmount runs the latest callback when the component unmounts.
Syntax
function useBeforeUnmount(fn: () => void): voidExample:
import { useBeforeUnmount } from '@pastweb/react';
function ExampleComponent() {
useBeforeUnmount(() => {
subscription.close();
});
return <div>Hello, world!</div>;
}useForceUpdate
useForceUpdate returns a stable function that forces the component to re-render.
Syntax
function useForceUpdate(): () => voidExample:
import { useForceUpdate } from '@pastweb/react';
function ExampleComponent() {
const forceUpdate = useForceUpdate();
return (
<div>
<p>Rendered at: {new Date().toLocaleTimeString()}</p>
<button onClick={forceUpdate}>Force Re-render</button>
</div>
);
}useMediator
useMediator creates a tools mediator inside React. Props and extras are wrapped in tools reactive objects, and mediator state is bridged into React rendering.
In development, useMediator detects Vite (import.meta.hot) and Webpack/Rspack-compatible (import.meta.webpackHot) HMR. When the mediator factory source changes, the mediator is recreated and its React state snapshot is refreshed, so Fast Refresh does not leave old mediator logic resident until the component remounts. Inline mediators keep their state across ordinary rerenders because the comparison is based on the mediator source signature, not only the function reference.
Syntax
function useMediator<T>(mediator: MediatorFunction<T>, props?: Props, extras?: Extras): T;Type Parameters
- T:
extends Mediator- A type that extends the Mediator type, representing the mediator object used for managing state, props, and logic.
Parameters
mediator:
MediatorFunction- A function that initializes and returns a mediator object managing state and logic. The mediator often encapsulates complex business logic.
props:
any & object = {}- Component props passed to the mediator. This defaults to an empty object if no props are provided.
extras:
T['extras'] = {} as T['extras']- Additional data passed to the mediator for handling extra configurations or logic. Defaults to an empty object.
Returns
An object that contains:
- state: The state managed by the mediator.
- props: The props passed to the mediator, including the component’s children.
- extras: The extras passed to the mediator.
- Additional properties defined by the mediator, excluding
state,props, andextras.
Example:
import { useMediator } from '@pastweb/react';
import { reactive } from '@pastweb/tools';
function myMediator(props, extras) {
const { initialCount } = props;
const { log } = extras;
const state = reactive({ count: initialCount });
function increment() {
state.count = state.count + 1;
if (log) console.log('count:', state.count);
}
return {
state,
props,
extras,
increment,
};
}
function Counter() {
const { state, increment } = useMediator(myMediator, { initialCount: 0 }, { log: true });
const { count } = state;
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}useMounted
useMounted runs a callback once after the component mounts. Async callbacks are allowed, but returned promises are not used as cleanup functions.
Syntax
function useMounted(fn: () => void | Promise<void>): void;Example:
import { useMounted } from '@pastweb/react';
function ExampleComponent() {
useMounted(() => {
analytics.track('mounted');
});
return <div>Hello, World!</div>;
}createMicroStore
createMicroStore takes the same parameters as createMicroStore from @pastweb/tools, creates the tools store, and returns a React-ready hook. The returned hook keeps the same readonly state/actions shape and re-renders after actions update selected state.
Syntax
function createMicroStore<S, A>(
name: string,
setup: (select: <T>(fn: Selector<T, S>) => T) => MicroStoreConfig<S, A>,
): ReactUseMicroStore<S, A>;Example:
import { createMicroStore } from '@pastweb/react';
const useCounterStore = createMicroStore('counter', () => ({
state: { count: 0 },
actions: {
increment() {
this.state.count += 1;
},
},
}));
function Counter() {
const counter = useCounterStore(state => state.count);
return (
<button onClick={counter.increment}>
{counter.state}
</button>
);
}reuseMicroStore
reuseMicroStore is the lower-level bridge for a tools store that was already created elsewhere. Use it when the store belongs to the framework-agnostic layer, but a React component needs to read it and re-render when the selected state changes.
Syntax
function reuseMicroStore<S, A, T = S>(
store: UseMicroStore<S, A>,
selector?: ReactMicroStoreSelector<T, S>,
): ReuseMicroStoreResult<T, A>Example:
import { createMicroStore } from '@pastweb/tools';
import { reuseMicroStore } from '@pastweb/react';
const settingsStore = createMicroStore('settings', () => ({
state: {
user: {
name: 'Ada',
preferences: {
theme: 'light',
},
},
},
actions: {
rename(name: string) {
this.state.user.name = name;
},
useDarkTheme() {
this.state.user.preferences.theme = 'dark';
},
},
}));
function UserSummary() {
const user = reuseMicroStore(settingsStore, state => state.user);
return (
<section>
<strong>{user.state.name}</strong>
<button onClick={user.useDarkTheme}>
Theme: {user.state.preferences.theme}
</button>
</section>
);
}The selector keeps the component focused on state.user, but the returned value still includes the store actions. Because the tools micro-store state is deeply reactive, changing user.preferences.theme refreshes the React component even though the store instance and selector are stable.
useRef
useRef creates a React ref with a value alias for current. Pastweb internals use value for cross-framework ref consistency, while the returned object still behaves like a normal React ref.
Syntax
function useRef<T>(value: T): RefObject<T> & { value: T };Example:
import { useRef } from '@pastweb/react';
function Counter() {
const count = useRef(0);
count.value += 1;
return <span>{count.current}</span>;
}Utility functions
Render
The Render component renders dynamic content from a string, number, React element, or React component.
Syntax
function Render(props: RenderProps): ReactElement | nullThe props object is optional and is applied when content is a React element or component.
Example:
import { Render } from '@pastweb/react';
function Status({ tone }: { tone: 'success' | 'warning' }) {
return <span>{tone}</span>;
}
function Example() {
return (
<>
<Render content="Ready" />
<Render content={<Status tone="warning" />} props={{ tone: 'success' }} />
<Render content={Status} props={{ tone: 'success' }} />
</>
);
}setRef
The setRef utility assigns a value to an object ref or callback ref. It is useful when a component needs to keep an internal ref and also forward the same node to a consumer-provided ref.
Syntax
function setRef<T>(ref: Ref<T> | MutableRefObject<T | null> | null | undefined, value: T | null): voidExample:
import { forwardRef, useCallback, useRef } from 'react';
import { setRef } from '@pastweb/react';
const ExampleComponent = forwardRef<HTMLDivElement>((props, forwardedRef) => {
const localRef = useRef<HTMLDivElement | null>(null);
const assignRef = useCallback((node: HTMLDivElement | null) => {
localRef.current = node;
setRef(forwardedRef, node);
if (localRef.current) {
localRef.current.focus();
}
}, [forwardedRef]);
return (
<div ref={assignRef} tabIndex={-1}>
Hello, world!
</div>
);
});It also works with callback refs:
Example:
setRef((node: HTMLDivElement | null) => {
if (node) {
node.focus();
}
}, document.createElement('div'));withDefaultProps
withDefaultProps merges props with defaults while preserving explicitly provided values. A default is used only when the prop value is undefined.
Syntax
function withDefaultProps<TProps>(props: Partial<TProps>, defaults: Partial<TProps>): TPropsExample:
import { withDefaultProps } from '@pastweb/react';
type ButtonProps = {
tone: 'neutral' | 'accent';
size: 'sm' | 'md';
};
function Button(props: Partial<ButtonProps>) {
const p = withDefaultProps<ButtonProps>(props, {
tone: 'neutral',
size: 'md',
});
return <button data-tone={p.tone}>{p.size}</button>;
}License
MIT License (c) 2026 Domenico Pasto
