475

useAsync

Hook that provides the state of an async callback

asyncmediumtest coverage

001

Loading

import { useAsync, useCounter } from '@siberiacancode/reactuse';
import { ArrowLeftIcon, ArrowRightIcon, Loader2Icon } from 'lucide-react';

interface Pokemon {
  base_experience: number;
  height: number;
  id: number;
  name: string;
  weight: number;
}

const getPokemon = async (id: number) => {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  if (id === 3) throw new Error('Pokemon blocked for demo');
  return fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then((res) =>
    res.json()
  ) as Promise<Pokemon>;
};

const Demo = () => {
  const counter = useCounter(1);
  const getPokemonQuery = useAsync(() => getPokemon(counter.value), [counter.value]);
  const formattedIndex = String(counter.value).padStart(3, '0');

  return (
    <section className='flex flex-col gap-4'>
      <div className='flex items-center justify-between gap-4'>
        <p className='font-mono font-bold'>{formattedIndex}</p>

        <div className='flex gap-2'>
          <button
            disabled={counter.value === 1 || getPokemonQuery.isLoading}
            type='button'
            onClick={() => counter.dec()}
          >
            <ArrowLeftIcon className='size-4' /> Prev
          </button>

          <button disabled={getPokemonQuery.isLoading} type='button' onClick={() => counter.inc()}>
            Next <ArrowRightIcon className='size-4' />
          </button>
        </div>
      </div>

      <div className='w-full min-w-xs rounded-lg border p-4 md:min-w-md'>
        {getPokemonQuery.isLoading && (
          <div className='flex h-32 flex-col items-center justify-center gap-2 md:h-44'>
            <Loader2Icon className='size-5 animate-spin' />
            <p>Loading</p>
          </div>
        )}

        {getPokemonQuery.error && !getPokemonQuery.isLoading && (
          <div className='flex h-32 items-center justify-center md:h-44'>
            <p className='text-destructive'>{getPokemonQuery.error.message}</p>
          </div>
        )}

        {getPokemonQuery.data && !getPokemonQuery.isLoading && !getPokemonQuery.error && (
          <div className='flex items-center justify-center gap-6 md:gap-10'>
            <div className='flex size-32 items-center justify-center md:size-44'>
              <img
                alt={getPokemonQuery.data.name}
                className='h-32 md:h-44'
                src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${getPokemonQuery.data.id}.png`}
              />
            </div>

            <div className='flex flex-col gap-2 text-sm md:text-base'>
              <p>
                name: <code>{getPokemonQuery.data.name}</code>
              </p>

              <p>
                height: <code>{getPokemonQuery.data.height}</code>
              </p>

              <p>
                weight: <code>{getPokemonQuery.data.weight}</code>
              </p>

              <p>
                experience: <code>{getPokemonQuery.data.base_experience}</code>
              </p>
            </div>
          </div>
        )}
      </div>
    </section>
  );
};

export default Demo;

Installation

pnpm add @siberiacancode/reactuse

Usage

const { data, isLoading, isError, error } = useAsync(() => fetch('url'), [deps]);

Type Declarations

import type { DependencyList } from 'react';

export interface UseAsyncReturn<Data> {
  /* The state of the query */
  data?: Data;
  /* The error of the query */
  error?: Error;
  /* The error state of the query  */
  isError: boolean;
  /* The loading state of the query */
  isLoading: boolean;
}

API

Parameters

NameTypeDefaultNote
callback() => Promise<Data>-The async callback
depsDependencyList[]The dependencies of the callback

Returns

UseAsyncReturn<Data>

Contributors

ddebabinbbabin

Last updated on