Skip to content

scality/react-chained-query

Repository files navigation

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-query

Quickstart

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>
  );
}

A complete example here

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

About

A wrapper of react-query useQuery hook allowing chaining queries.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •