-
Notifications
You must be signed in to change notification settings - Fork 0
useResourceCompose
The useResourceCompose hook allows you to combine, merge, and derive reactive state from multiple independent useResource instances. It automatically subscribes to all dependent resources and re-computes the derived state whenever any dependency emits new data, with custom equality comparisons to prevent unnecessary component re-renders.
Alternatively accessible via the static alias useResource.compose().
The simplest way to use useResourceCompose is to provide a unique cache key, a map of dependent deps, and a selector function.
import { useResource, useResourceCompose } from 'react-hook-lab';
function UserStats({ userId }) {
const user = useResource({
key: `user:${userId}`,
fetcher: () => fetch(`/api/users/${userId}`).then(res => res.json()),
});
const posts = useResource({
key: `posts:${userId}`,
fetcher: () => fetch(`/api/users/${userId}/posts`).then(res => res.json()),
});
// Compose user and posts into a single summary resource
const summary = useResourceCompose({
key: `summary:${userId}`,
deps: { user, posts },
selector: ({ user, posts }) => ({
name: user?.name ?? "Guest",
postCount: posts?.length ?? 0,
}),
});
if (summary.loading && !summary.data) return <div>Loading summary...</div>;
if (summary.error) return <div>Error loading summary</div>;
return (
<div>
<h3>{summary.data?.name}</h3>
<p>Total Posts: {summary.data?.postCount}</p>
</div>
);
}In real-world applications, you often need to derive data from disparate endpoints (e.g. auth user and shopping cart) and handle aggregated loading and error states cleanly.
import { useResource, useResourceCompose } from 'react-hook-lab';
function HeaderCart() {
const auth = useResource({
key: 'auth:current-user',
fetcher: () => fetch('/api/me').then(r => r.json()),
});
const cart = useResource({
key: 'cart:items',
fetcher: () => fetch('/api/cart').then(r => r.json()),
});
const cartSummary = useResourceCompose({
key: 'cart:summary',
deps: { auth, cart },
selector: ({ auth, cart }) => {
const items = cart?.items ?? [];
const totalAmount = items.reduce((sum, i) => sum + i.price * i.qty, 0);
return {
userName: auth?.username,
itemCount: items.length,
totalAmount,
hasDiscount: (auth?.tier === 'vip') && totalAmount > 100,
};
},
});
return (
<header>
<span>Hello, {cartSummary.data?.userName || "Shopper"}</span>
<span>Cart ({cartSummary.data?.itemCount || 0} items)</span>
<span>Total: ${cartSummary.data?.totalAmount || 0}</span>
{cartSummary.data?.hasDiscount && <span>VIP Discount Applied!</span>}
</header>
);
}To optimize performance for high-frequency updates, useResourceCompose accepts an equalityFn (defaults to deepEqual) to skip state updates when the selected value remains structurally identical.
import { useResource, deepEqual } from 'react-hook-lab';
function HighPerformanceView() {
const resourceA = useResource({ key: 'source-a', fetcher: fetchA });
const resourceB = useResource({ key: 'source-b', fetcher: fetchB });
// Can be called directly via useResource.compose()
const composed = useResource.compose({
key: 'composed:performance',
deps: { resourceA, resourceB },
selector: ({ resourceA, resourceB }) => {
return {
aId: resourceA?.id,
bCount: resourceB?.count,
};
},
// Custom fine-grained equality check to avoid re-renders
equalityFn: (prev, next) => {
return prev?.aId === next?.aId && prev?.bCount === next?.bCount;
},
});
return (
<div>
<p>ID: {composed.data?.aId}</p>
<p>Count: {composed.data?.bCount}</p>
</div>
);
}useResourceCompose<TDeps, R>(config) takes an object:
-
key: string(required): The unique key identifying the derived resource. -
deps: TDeps(required): A record/object mapping arbitrary names toResource<any>instances. -
selector: (values: { [K in keyof TDeps]: TDeps[K]["data"] }) => R(required): Pure transformer function that receives the resolved.datafrom each dependency and returns the derived output. -
equalityFn?: (a: R, b: R) => boolean(optional): Comparison function used to determine if the derived output actually changed. Defaults todeepEqual.
Returns a standard Resource<R> instance representing the derived state:
-
data: R | undefined: The latest derived output fromselector. -
loading: boolean: True if the composed resource is initializing or any dependency is loading without data. -
error: unknown | undefined: Captured errors if any dependency or selector fails. -
status: "idle" | "loading" | "error" | "success" | "refreshing": Current status. -
mutate(updater): Manually override or update the composed state. -
refresh(): Force a re-computation of the derived state.
Built with ❤️ by Saurav-TB-Pandey. | Report an Issue | NPM