npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-async-task

v1.0.0

Published

Precise async tasks for React Hooks

Downloads

2

Readme

useAsyncTask

useAsyncTask makes it easy to manage asynchronous calls in React Hooks. You know, like for calling APIs? That thing everyone needs to do?

It's a lightweight, one-file solution with zero dependencies, and it's been battle-tested for years.

For more on why I wrote this, see the blog post.

Installation

npm install use-async-task

Usage

Here's an example of using it with fetch():

import React from "react";
import { useAsyncTask } from "use-async-task";

function BookList() {
  const getBooks = useAsyncTask({
    async func() {
      const result = await fetch("https://www.anapioficeandfire.com/api/books");
      return await result.json();
    },
  });

  return (
    <>
      <ul>
        {getBooks.result?.map((book) => (
          <li key={book.url}>{book.name}</li>
        ))}
      </ul>
      <button onClick={getBooks.run}>Submit</button>
    </>
  );
}

API

You can pass more (optional) arguments to useAsyncTask():

const getBooks = useAsyncTask({
  async func() {
    // do work, return result if needed
  },
  // Runs the task function (with no arguments) when
  // the component mounts.
  runOnMount: true,
  // Leaves the `isRunning` flag set to `true` after the
  // task function completes. This is useful if you want
  // to leave your UI in a "working" state while making it
  // animate away, for instance if it's in a modal.
  leaveRunning: true,
  // Called when the task function starts.
  onStart: () => {},
  // Called when the task function completes.
  onComplete: (result) => {},
  // Called when the task function throws an error.
  onError: (error) => {},
  // Called when the task function completes or throws an error.
  onFinally: () => {},
});

The useAsyncTask() hook returns an object with the following properties:

{
  // Runs the task function, with optional arguments. In
  // TypeScript, the arguments will be typed as the
  // arguments of the task function.
  run: (...args) => void,
  // Cancels the task function (if it's running). This is always
  // safe to call, even if the task function isn't running.
  cancel: () => void,
  // Whether the task function is currently running.
  isRunning: boolean,
  // The result of the task function, if it completed. In
  // TypeScript, this will be typed as the return type of
  // the task function.
  result: T,
  // The error thrown by the task function, if it threw an error.
  error: Error,
}

Handling Errors

In TypeScript, you must provide a value for the onError argument:

const getBooks = useAsyncTask({
  async func() {
    // do work
  },
  onError(error: Error) {
    // make sure the user sees this error!
  }
});

You can say onError: null to silence the warning if you are checking the getBooks.error value in render instead. But we wanted to make it very difficult for (usually rare) errors to be silently ignored because a developer forgot to handle them.

Canceling Tasks

If you want to cancel a task, you can call cancel() on the task function. This will cause any result of the task function to be discarded, and the onComplete() and onFinally() callbacks will not be called.

You can also discover from within your task function whether it has been canceled by checking the special this.isCanceled() function:

const getBooks = useAsyncTask({
  async func() {
    const result = await fetch("https://www.anapioficeandfire.com/api/books");

    // Were we canceled?
    if (this.isCanceled()) return;

    return await result.json();
  }
});

NOTE: For this to work, you need to define your task function like this:

const getBooks = useAsyncTask({
  async func() {
    if (this.isCanceled()) { /* ... */ }
  }
});

If you define your task function as an arrow function, this will not work:

const getBooks = useAsyncTask({
  func: async () => {
    // this.isCanceled() will not work here!
  }
});

The unusual technique of using this to access the task function's state was the most elegant way I could come up with, due to limitations of TypeScript argument types, closures, and the possibility of multiple task invocations. If you have a better idea, please let me know!

Multiple Invocations

It is safe to call run() multiple times even before the task is finished. There is no way in JavaScript to "kill" a running task, so the task will continue to run in the background (or until you check isCanceled()). But the onComplete() and onFinally() callbacks will only be called for the most recent invocation.

In the rare instance that you need to know which invocation of the task function is currently running, you can check the this.invocation property from within the task function. This is a number that increments every time the task function is invoked.