# Async Patterns in React

Canonical: https://gaboesquivel.com/blog/2023-09-async-patterns-in-reactjs

**Published:** 2023-09-05
**Summary:** How I keep asynchronous logic in TypeScript services and connect it to React with TanStack Query and reusable hooks.
**Categories:** Engineering, Frontend
**Technologies:** ReactJS, TypeScript, NextJS, TanStack Query

Updated in May 2024

On [bitlauncher.ai](https://bitlauncher.ai) I started using Wagmi, and I like its design enough that I now reach for `useQuery` more often than the `useAsync` helpers in the PolkadotJS examples below.

### TanStack for async operations

In [React](https://gaboesquivel.com/tech/reactjs), I use TanStack Query (formerly React Query) for asynchronous operations and server state, because it handles caching, request deduplication, and persistence.

[Wagmi](https://gaboesquivel.com/tech/wagmi) is built on TanStack Query for those same primitives. It ships over 20 hooks for wallets, ENS, contracts, transactions, signing, and more, plus connectors for injected providers, WalletConnect, MetaMask, and Coinbase Wallet. Coinbase, [Stripe](https://gaboesquivel.com/tech/stripe), Shopify, Uniswap, Optimism, ENS, and Sushi use it in production.

The [Parity Capi Multisig App](https://github.com/paritytech/capi-multisig-app) also uses TanStack internally.

The rest of this post is the older PolkadotJS pattern: keep core logic in vanilla [TypeScript](https://gaboesquivel.com/tech/typescript) and use `useAsync` / `useAsyncFn` at the React edge.

A services directory holds plain functions that talk to external APIs and storage: HTTP requests, WebSocket connections, web storage, and API queries. The examples that follow consume a vanilla PolkadotJS service from React components.

```tsx
import { ApiPromise, WsProvider } from '@polkadot/api';
import { executePromisesInSeries } from '~/lib/async';

const wsProvider = new WsProvider('wss://rpc.polkadot.io');

export async function getApi(){
  return ApiPromise.create({ provider: wsProvider });
}

export async function getChain(){
  const api = await getApi();
  return api.rpc.system.chain();
}

export async function getLatestHeader() {
  const api = await getApi();
  return api.rpc.chain.getHeader();
}

export async function getGenisisHash(){
  const api = await getApi();
  return api.genesisHash.toHex();
}

export async function someVanillaSeries(){
  const tasks = [
    getChain, 
    getGenisisHash, 
    getChain
  ];
  return executePromisesInSeries(tasks);
}

export async function someVanillaParallel(){
  const result = await Promise.all([getGenisisHash(), getChain()]);
  return await result;
}

export async function someVanillaConditional(){
  const chain = await getChain();
  const genesisHash = chain ? await getGenisisHash() : null;
  return genesisHash;
}
```

To avoid reconnecting to the WebSocket on every service call, you can keep a reference to the connected API client in the global window scope, for instance `window.myappscope.api`.

## Async calls when a component mounts

```tsx
import { useAsync } from "~/hooks/use-async-fn";
import { someVanillaConditional, someVanillaParallel, someVanillaSeries } from "~/services/chain";

export function AsyncOnComponentMounted(){
  const conditionalCallState = useAsync(someVanillaConditional);
  const parallelCallState = useAsync(someVanillaParallel);
  const seriesCallState = useAsync(someVanillaSeries);
  
  return (
    
      Async on Component Mounted Conditionally
      {JSON.stringify(conditionalCallState)}

      Async on Component Mounted in Parallel
      {JSON.stringify(parallelCallState, null, 2)}

      Async on Component Mounted in Series
      {JSON.stringify(seriesCallState, null, 2)}
    
  );
}
```

## Async calls on browser events

```tsx
import { useAsyncFn } from "~/hooks/use-async-fn";
import { someVanillaConditional, someVanillaParallel, someVanillaSeries } from "~/services/chain";

export function AsyncOnEvent(){
  const [conditionalCallState, callConditionalFn] = useAsyncFn(someVanillaConditional);
  const [parallelCallState, callParallelFn] = useAsyncFn(someVanillaParallel);
  const [seriesCallState, callSeriesFn] = useAsyncFn(someVanillaSeries);
  
  return (
    
      Async on Event Conditionally
      Get data conditionally
      {JSON.stringify(conditionalCallState)}

      Async on Event in Parallel
      Get data in parallel
      {JSON.stringify(parallelCallState, null, 2)}

       Async on Event in Series
      Get data in series
      {JSON.stringify(seriesCallState, null, 2)}
    
  );
}
```

The `useAsync` and `useAsyncFn` hooks come from [react-use](https://github.com/streamich/react-use), and they handle errors internally, so each call state carries `{ error, loading, data }` from the async operation and the component needs no try/catch blocks or manual loading flags. More examples are in the [GitHub repository](https://github.com/gaboesquivel/react-async).