• Stars
    star
    497
  • Rank 85,470 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 5 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

[NOT MAINTAINED] React custom hooks for async functions with abortability and composability

This project is no longer actively maintained. The approach this project takes is so-called useEffect chaining, which will not be a best pracitce in the future versions of React. This would be still useful for learning and might work for small projects. React community should move to new data fetching approach, or at least approach to fire a single async function in useEffect. See also: #64


react-hooks-async

Build Status npm version bundle size

React custom hooks for async functions with abortability and composability

Introduction

JavaScript promises are not abortable/cancelable. However, DOM provides AbortController which can be used for aborting promises in general.

This is a library to provide an easy way to handle abortable async functions with React Hooks API.

It comes with a collection of custom hooks that can be used as is. More custom hooks can be developed based on core hooks.

Install

npm install react-hooks-async

Usage

A basic async example (run immediately)

import React from 'react';

import { useAsyncTask, useAsyncRun } from 'react-hooks-async';

const fetchStarwarsHero = async ({ signal }, id) => {
  const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal });
  const data = await response.json();
  return data;
};

const StarwarsHero = ({ id }) => {
  const task = useAsyncTask(fetchStarwarsHero);
  useAsyncRun(task, id);
  const { pending, error, result, abort } = task;
  if (pending) return <div>Loading...<button onClick={abort}>Abort</button></div>;
  if (error) return <div>Error: {error.name} {error.message}</div>;
  return <div>Name: {result.name}</div>;
};

const App = () => (
  <div>
    <StarwarsHero id={'1'} />
    <StarwarsHero id={'2'} />
  </div>
);

A basic async example (run in callback)

import React, { useState } from 'react';

import { useAsyncTask } from 'react-hooks-async';

const fetchStarwarsHero = async ({ signal }, id) => {
  const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal });
  const data = await response.json();
  return data;
};

const StarwarsHero = () => {
  const { start, started, result } = useAsyncTask(fetchStarwarsHero);
  const [id, setId] = useState('');
  return (
    <div>
      <input value={id} onChange={e => setId(e.target.value)} />
      <button type="button" onClick={() => start(id)}>Fetch</button>
      {started && 'Fetching...'}
      <div>Name: {result && result.name}</div>
    </div>
  );
};

const App = () => (
  <div>
    <StarwarsHero />
    <StarwarsHero />
  </div>
);

A simple fetch example

import React from 'react';

import { useFetch } from 'react-hooks-async';

const UserInfo = ({ id }) => {
  const url = `https://reqres.in/api/users/${id}?delay=1`;
  const { pending, error, result, abort } = useFetch(url);
  if (pending) return <div>Loading...<button onClick={abort}>Abort</button></div>;
  if (error) return <div>Error: {error.name} {error.message}</div>;
  return <div>First Name: {result.data.first_name}</div>;
};

const App = () => (
  <div>
    <UserInfo id={'1'} />
    <UserInfo id={'2'} />
  </div>
);

A typeahead search example using combination

Preview

import React, { useState, useCallback } from 'react';

import {
  useAsyncCombineSeq,
  useAsyncRun,
  useAsyncTaskDelay,
  useAsyncTaskFetch,
} from 'react-hooks-async';

const Err = ({ error }) => <div>Error: {error.name} {error.message}</div>;

const Loading = ({ abort }) => <div>Loading...<button onClick={abort}>Abort</button></div>;

const GitHubSearch = ({ query }) => {
  const url = `https://api.github.com/search/repositories?q=${query}`;
  const delayTask = useAsyncTaskDelay(500);
  const fetchTask = useAsyncTaskFetch(url);
  const combinedTask = useAsyncCombineSeq(delayTask, fetchTask);
  useAsyncRun(combinedTask);
  if (delayTask.pending) return <div>Waiting...</div>;
  if (fetchTask.error) return <Err error={fetchTask.error} />;
  if (fetchTask.pending) return <Loading abort={fetchTask.abort} />;
  return (
    <ul>
      {fetchTask.result.items.map(({ id, name, html_url }) => (
        <li key={id}><a target="_blank" href={html_url}>{name}</a></li>
      ))}
    </ul>
  );
};

const App = () => {
  const [query, setQuery] = useState('');
  return (
    <div>
      Query:
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {query && <GitHubSearch query={query} />}
    </div>
  );
};

Examples

The examples folder contains working examples. You can run one of them with

PORT=8080 npm run examples:01_minimal

and open http://localhost:8080 in your web browser.

You can also try them in codesandbox.io: 01 02 03 04 05 06 07 08 09 10

Reference

Note: Almost all hooks check referential equality of arguments. Arguments must be memoized if they would change in re-renders. Consider defining them outside of render, or useMemo/useMemoOne/useCallback/useCallbackOne.

States

State Description
started Initial false. Becomes true once the task is started. Becomes false when the task ends
pending Initial true. Stays true after the task is started. Becomes false when the task ends

An example,

  • initial: started=false, pending=true
  • first start: started=true, pending=true
  • first end: started=false, pending=false
  • second start: started=true, pending=true
  • second end: started=false, pending=false

Core hooks

useAsyncTask

const task = useAsyncTask(func);

This function is to create a new async task.

The first argument func is a function with an argument which is AbortController. This function returns a promise, but the function is responsible to cancel the promise by AbortController. If func receives the second or rest arguments, those can be passed by useAsyncRun(task, ...args) or task.start(...args).

When func is referentially changed, a new async task will be created.

The return value task is an object that contains information about the state of the task and some internal information. The state of the task can be destructured like the following:

const { pending, error, result } = task;

When a task is created, it's not started. To run a task, either call useAsyncRun(task, [...args]) in render, or call task.start([...args]) in callback.

useAsyncRun

useAsyncRun(task, ...args);

This function is to run an async task. When the task is updated, this function aborts the previous running task and start the new one.

The first argument task is an object returned by useAsyncTask and its variants. This can be a falsy value and in that case it won't run any tasks. Hence, it's possible to control the timing by:

useAsyncRun(ready && task);

The second or rest arguments are optional. If they are provided, the referential equality matters, so useMemo/useMemoOne would be necessary.

The return value of this function is void. You need to keep using task to get the state of the task.

Combining hooks

useAsyncCombineSeq

const combinedTask = useAsyncCombineSeq(task1, task2, ...);

This function combines multiple tasks in a sequential manner.

The arguments task1, task2, ... are tasks created by useAsyncTask. They shouldn't be started.

The return value combinedTask is a newly created combined task which holds an array of each task results in the result property.

useAsyncCombineAll

const combinedTask = useAsyncCombineAll(task1, task2, ...);

This function combines multiple tasks in a parallel manner.

The arguments and return value are the same as useAsyncCombineSeq.

useAsyncCombineRace

const combinedTask = useAsyncCombineRace(task1, task2, ...);

This function combines multiple tasks in a "race" manner.

The arguments and return value are the same as useAsyncCombineSeq.

Helper hooks

These hooks are just wrappers of useAsyncTask.

useAsyncTaskTimeout

const task = useAsyncTaskTimeout(func, delay);

This function returns an async task that runs func after delay ms.

When func is referentially changed, a new async task will be created.

useAsyncTaskDelay

const task = useAsyncTaskDelay(delay);

This function returns an async task that finishes after delay. This is a simpler variant of useAsyncTaskTimeout. delay is either a number or a function that returns a number.

When delay is referentially changed, a new async task will be created.

useAsyncTaskFetch

const task = useAsyncTaskFetch(input, init, bodyReader);

This function returns an async task that runs fetch. The first argument input and the second argument init are simply fed into fetch. The third argument bodyReader is to read the response body, which defaults to JSON parser.

When input or other arguments is referentially changed, a new async task will be created.

The hook useFetch has the same signature and runs the async task immediately.

useAsyncTaskAxios

const task = useAsyncTaskAxios(axios, config);

This is similar to useAsyncTaskFetch but using axios.

When config or other arguments is referentially changed, a new async task will be created.

The hook useAxios has the same signature and runs the async task immediately.

useAsyncTaskWasm

const task = useAsyncTaskWasm(input, importObject);

This function returns an async task that fetches wasm and creates a WebAssembly instance. The first argument input is simply fed into fetch. The second argument importObject is passed at instantiating WebAssembly.

When input or other arguments is referentially changed, a new async task will be created.

The hook useWasm has the same signature and runs the async task immediately.

Limitations

  • Due to the nature of React Hooks API, creating async tasks dynamically is not possible. For example, we cannot create arbitrary numbers of async tasks at runtime. For such a complex use case, we would use other solutions including upcoming react-cache and Suspense.

Blogs

More Repositories

1

waku

⛩️ The minimal React framework
TypeScript
3,473
star
2

react-tracked

State usage tracking with Proxies. Optimize re-renders for useState/useReducer, React Redux, Zustand and others.
TypeScript
2,594
star
3

use-context-selector

React useContextSelector hook in userland
TypeScript
2,414
star
4

excalidraw-animate

A tool to animate Excalidraw drawings
TypeScript
1,121
star
5

react-hooks-global-state

[NOT MAINTAINED] Simple global state for React with Hooks API without Context API
TypeScript
1,104
star
6

react-hooks-worker

React custom hooks for web workers
TypeScript
696
star
7

proxy-memoize

Intuitive magical memoization library with Proxy and WeakMap
TypeScript
667
star
8

will-this-react-global-state-work-in-concurrent-rendering

Test tearing and branching in React concurrent rendering
JavaScript
541
star
9

reactive-react-redux

[NOT MAINTAINED] React Redux binding with React Hooks and Proxy
TypeScript
502
star
10

excalidraw-claymate

A tool based on Excalidraw to create stop motion animations and slides.
TypeScript
457
star
11

react-hooks-fetch

Minimal data fetching library with React Suspense
TypeScript
398
star
12

react-worker-components

React Worker Components simplify using Web Workers
TypeScript
315
star
13

react-suspense-fetch

[NOT MAINTAINED] A low-level library for React Suspense for Data Fetching
TypeScript
296
star
14

katachidraw

SVG based drawing tool and react-native component
TypeScript
282
star
15

redux-in-worker

Entire Redux in Web Worker
TypeScript
258
star
16

proxy-compare

Compare two objects using accessed properties with Proxy
TypeScript
252
star
17

social-cms-backend

Express middleware to provide schema-less REST APIs for creating a social networking website primarily using angular.js. It comes with built-in authentication, authorization and notification features.
JavaScript
216
star
18

valtio-yjs

valtio-yjs makes yjs state easy
TypeScript
177
star
19

use-reducer-async

React useReducer with async actions
TypeScript
176
star
20

connect-prerenderer

Express/connect middleware to pre-render ajax page for non-ajax browsers, especially using angular.js
JavaScript
161
star
21

typescript-expo-apollo-boilerplate

[NOT MAINTAINED] Clean boilerplate for TypeScript + Expo (React Native) + React Apollo (GraphQL)
TypeScript
145
star
22

es-beautifier

[NOT MAINTAINED] ECMAScript beautifier based on eslint
JavaScript
121
star
23

zustand-signal

Another React binding for Zustand
TypeScript
109
star
24

use-atom

Yet another implementation for Jotai atoms without side effects
TypeScript
108
star
25

create-react-signals

A factory function to create signals for React
TypeScript
107
star
26

remote-faces

A tool for Working From Home: Share your webcam images with your colleagues
JavaScript
94
star
27

react-suspense-router

[NOT MAINTAINED] React Router for React Suspense and Render-as-You-Fetch
TypeScript
88
star
28

continuation.js

A module for tail call optimization by Continuation Passing Style (CPS) transformation with trampoline technique for Node.js
JavaScript
77
star
29

rss-pipes

RSS feed aggregator by Node.js
JavaScript
74
star
30

derive-zustand

A function to create a derived Zustand store from stores
TypeScript
71
star
31

use-zustand

Another custom hook to use Zustand vanilla store
TypeScript
57
star
32

lets-compare-global-state-with-react-hooks

Comparing global state libraries with React Hooks
57
star
33

easy-livereload

Express middleware to use livereload2 easily (both server and client)
JavaScript
56
star
34

codeonmobile

A coding tool on mobile devices targeting GitHub/Codeship/Heroku
JavaScript
55
star
35

valtio-signal

Another React binding for Valtio proxy state
TypeScript
50
star
36

connect-cache-manifest

Express/connect middleware to generate HTML5 cache manifest file.
JavaScript
49
star
37

use-signals

An experimental React hook for TC39 signals
JavaScript
40
star
38

notes-app-sample

A sample HTML5 web app using social-cms-backend (the BMEAN stack)
JavaScript
33
star
39

excalidraw-gallery

A tool to display Excalidraw drawings
JavaScript
32
star
40

express-react-redux

Express middleware for React/Redux applications
JavaScript
30
star
41

svelte3-redux

[NOT MAINTAINED] Redux for Svelte 3
TypeScript
30
star
42

excalidraw-layers

A tool to view layers of Excalidraw drawings
TypeScript
26
star
43

react-suspense-worker

[NOT MAINTAINED] React Suspense for Web Worker with Comlink
TypeScript
26
star
44

react-native-dom-expo

[NOT MAINTAINED] A patch library to make Expo work with react-native-dom
JavaScript
25
star
45

react-context-global-state

[NOT MAINTAINED] Simple global state for React with Context API
JavaScript
23
star
46

gqless-hook

[NOT MAINTAINED] Yet another React hook for gqless
TypeScript
22
star
47

use-valtio

Another custom hook to use Valtio proxy state
TypeScript
21
star
48

react-hooks-render-props

[NOT MAINTAINED] A hacking custom hook to emulate render props
JavaScript
18
star
49

benchmark-octane

Octane benchmark for Node.js
JavaScript
18
star
50

recoildux

[NOT MAINTAINED] Recoil inspired implementation with Redux
TypeScript
16
star
51

react-compose-state

A helper function to attach state to stateless function components
JavaScript
16
star
52

gunosy-rss

[OBSOLETE] Gunosy RSS feed web service by Node.js
JavaScript
15
star
53

apollo-link-lazy

Apollo Link for lazy loading
JavaScript
14
star
54

react-suspense-router-demo

[NOT MAINTAINED] A demo app with react-suspense-router
TypeScript
11
star
55

react-apollo-github-api-infinite-scroll-example

React Apollo GitHub GraphQL API infinite scroll example code
JavaScript
11
star
56

twitter-clone-sample

JavaScript
7
star
57

blog

Personal blog
Less
6
star
58

waku-vercel-starter

TypeScript
5
star
59

meteor-fan

HTML
4
star
60

dai-shi

3
star
61

waku-netlify-starter

TypeScript
3
star
62

minimal-apollo-example

Minimal Apollo stack example code
JavaScript
2
star
63

react-compose-onmount

A helper function to attach onmount handler to stateless function components
JavaScript
2
star
64

GBrain

A Google AppEngine web app for sharing brains.
Java
1
star
65

meteor-blaze-showhide

Handy block helpers for show/hide functionality in Blaze
JavaScript
1
star
66

meteor-google-maps-example

JavaScript
1
star
67

meteor-blaze-google-maps

Easy Blaze template for Google Maps with reactivity
JavaScript
1
star
68

meteor-facebook-server-api

A Meteor package to provide Facebook Graph API
JavaScript
1
star