@scality/react-chained-query
v2.0.1
Published
A wrapper of react-query useQuery hook allowing chained queries.
Readme
react-chained-query
Features
useChainedQuery
useChainedQuery hook consit of a wrapper on top of react-query useQuery. This useChainedQuery hook allow chaining queries instead of runnning them concurently, it aims to solve problems that may occurs when hitting a slow backend with too many requests.
By managing a queue and executing the request one after another, it could give the capability for an application to display the information sequentially.
useChainedMutations
useChainedMutations hook chains mutations sequentially with retry support. It supports both static (pre-created) and dynamic (hook-based) mutations. Each step receives results from previous mutations to compute its variables.
Install
npm install @scality/react-chained-queryQuickstart
useChainedQuery
import { QueryClient, QueryClientProvider } from 'react-query';
import { ChainedQueryProvider, useChainedQuery } from '@scality/react-chained-query';
const queryClient = new QueryClient();
function Component1() {
const { data } = useChainedQuery({
queryKey: ['key', 'arg'],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 2_000));
return '1';
},
});
return <>{data}</>;
}
function Component2() {
const { data } = useChainedQuery({
queryKey: ['key', 'arg1'],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1_000));
return '2';
},
});
return <>{data}</>;
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<ChainedQueryProvider>
<div className="App">
<h2>Hello, useChainedQuery! </h2>
<Component1 />
<Component2 />
</div>
</ChainedQueryProvider>
</QueryClientProvider>
);
}useChainedMutations
Basic Usage (Static Mutations)
import { useMutation } from 'react-query';
import { useChainedMutations } from '@scality/react-chained-query';
const useUpdatePost = () => {
return useMutation({
mutationFn: async (id: string) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'PUT',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify({ id, title: 'foo', body: 'bar', userId: id }),
});
if (!res.ok) throw res.statusText;
return res.json();
},
});
};
export default function App() {
const updateUser1 = useUpdatePost();
const updateUser2 = useUpdatePost();
const { steps, isComplete, hasError, start } = useChainedMutations({
mutations: [
{ id: 'user1', label: 'Update User 1', mutation: updateUser1 },
{ id: 'user2', label: 'Update User 2', mutation: updateUser2 },
],
variables: {
user1: () => '1',
user2: (prev) => prev.user1.data.userId, // Access by key (recommended)
// Or: (prev) => prev[0].data.userId // Access by index (still supported)
},
autoStart: false,
});
return (
<div>
<button onClick={start}>Start</button>
<ul>
{steps.map((step) => (
<li key={step.id}>
{step.label}: {step.status}
{step.status === 'error' && <button onClick={step.retry}>Retry</button>}
</li>
))}
</ul>
{isComplete && <p>Done!</p>}
{hasError && <p>Error occurred</p>}
</div>
);
}Dynamic Mutations (for dynamic lists)
When you need to create mutations from a dynamic array (e.g., user-selected items), use hook instead of mutation:
const userIds = ['1', '2', '3']; // Could come from props or state
const { Slots, steps, start } = useChainedMutations({
mutations: userIds.map((id) => ({
id: `user-${id}`,
label: `Update User ${id}`,
hook: useUpdatePost, // Hook will be called internally for each mutation
})),
variables: Object.fromEntries(
userIds.map((id, i) => [`user-${id}`, (prev) => (i === 0 ? id : prev[i - 1].data.userId)])
),
});
return (
<>
{Slots} {/* Required: renders hidden components that call hooks for dynamic mutations */}
<button onClick={start}>Start</button>
</>
);API
| Config | Type | Description |
|--------|------|-------------|
| mutations | MutationConfig[] | Array with id, label, and either mutation (static) or hook (dynamic) |
| variables | Record<string, (prev) => unknown> | Functions to compute variables. Access results via prev.mutationId.data (recommended) or prev[i].data. Note: Mutation ids should not be numeric strings ("0", "1") or array method names ("length", "push", "map", etc.) as they conflict with array properties. |
| autoStart | boolean | Auto-start when ready. Default: true |
| Return | Type | Description |
|--------|------|-------------|
| Slots | ReactNode | Render this for dynamic mutations |
| steps | StepStatus[] | { id, label, step, status, retry } |
| isReady | boolean | All mutations registered |
| isComplete | boolean | All succeeded |
| hasError | boolean | Any failed |
| start | () => void | Manual start |
| reset | () => void | Reset chain state, allows start() again |
| getResult | <T>(id: string) => T | Get mutation result |
Advanced Documentation
In order to use useChainedQuery in your component, it has be below QueryClientProvider and ChainedQueryProvider.
It's possibile to have several ChainedQueryProvider each of them would then holds it's own queue of queries.
<QueryClientProvider>
<ChainedQueryProvider>
<YourComponent />
</ChainedQueryProvider>
</QueryClientProvider>Made with ❤️ by Pod-UI at Scality
