Playing with an async task in React
npm install react-hook-use-async!npm    !npm bundle size !GitHub
Give me an async task, I'll give you its insights!
- Features
- Installation
- Problem
- Examples
- API
- Feature Comparison
- Migrating from v1 to v2
- FAQ
- Demo
- License
- Simple and flexible!
- Render async task's result without headache.
- Get notified when the async task is complete via onSuccess(), onError(), onCancel() callbacks.
- Support automatically and on-demand re-execute via execute().
- Support automatically and on-demand cancellation via cancel().
``shell`
$ npm install --save react-hook-use-async
Async task is a very common stuff in application. For example, fetch todo list, follow a person, upload images... and we need a convenient way to execute an async task and render its result when the task is complete. This package provides a convenient hook to deal with them. Let's see examples and FAQ below.
`jsx
import useAsync from 'react-hook-use-async';
function fetchUserById([id]) {
const url = http://example.com/fetch-user?id=${id};
return fetch(url).then(response => response.json());
}
function User({ id }) {
const task = useAsync(fetchUserById, [id]);
return (
$3
`jsx
import useAsync from 'react-hook-use-async';function followUserById([id]) {
const url = 'http://example.com/follow-user';
const config = {
method: 'POST',
body: JSON.stringify({ id }),
};
return fetch(url, config);
}
function FollowUserBtn({ id }) {
const task = useAsync(followUserById, [id], { isOnDemand: true });
return (
);
}
`$3
`jsx
import useAsync from 'react-hook-use-async';function FollowUserBtn({ id }) {
const task = useAsync(followUserById, [id], {
isOnDemand: true,
onSuccess: (result, [id]) => {
const message =
Followed user ${id}, you are number ${result.rank} in fan-ranking;
console.log(message);
},
onError: (error, [id]) => {
const message = Got error while trying to follow user ${id}: ${error.message};
console.log(message);
},
onCancel: ([id]) => {
const message = Canceled following user ${id};
console.log(message);
},
});
return (
);
}
`$3
`jsx
import useAsync, { Task } from 'react-hook-use-async';function fetchUserById([id]) {
const url =
http://example.com/fetch-user?id=${id};
const controller = (typeof AbortController !== 'undefined' ? new AbortController() : null);
const config = { signal: controller && controller.signal };
const promise = fetch(url, config).then(response => response.json());
const cancel = () => controller && controller.abort();
return new Task(promise, cancel);
}function User({ id }) {
const task = useAsync(fetchUserById, [id]);
...
}
`$3
`jsx
import useAsync, { Task } from 'react-hook-use-async';function fetchArticles([query]) {
const url =
http://example.com/fetch-articles?query=${query};
const controller = (typeof AbortController !== 'undefined' ? new AbortController() : null);
const config = { signal: controller && controller.signal };
let timeoutId = null;
const promise = new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
fetch(url, config).then(response => response.json()).then(resolve).catch(reject);
}, 300);
});
const cancel = () => {
timeoutId && clearTimeout(timeoutId);
controller && controller.abort();
};
return new Task(promise, cancel);
}function Articles({ query }) {
const task = useAsync(fetchArticles, [query]);
...
}
`API
$3
`js
const {
// async task error, default: undefined
error,
// async task result, default: undefined
result,
// promise of current async task, default: undefined
promise,
// async task is pending or not, default: false
isPending,
// cancel current async task on-demand
cancel,
// execute current async task on-demand
execute,
} = useAsync(
// (inputs) => Promise
//
// A function which get called to create async task, using inputs.
//
// It should return a promise, but actually it can return any value.
createTask, // any[], default: []
//
// This array is used to create task. If it changes, the old task will
// be canceled and a new task will be created. If your inputs shape
// seems unchange but a new task still be created infinitely, the reason
// is because React uses
Object.is() comparison algorithm, shallow
// comparison. In short, inputs must be an array of primitives, else
// you should memoize non-primitive values for yourself.
//
// Other note: size MUST BE consistent between renders!
inputs, // optional config
config: {
// boolean, default: false
//
// This flag provides two modes.
// In proactive mode (false), an async task will be executed automatically when
inputs changes.
// In on-demand mode (true), an async task will be executed only when execute() get called.
//
// Note: only its value in the first render will be used. This means mode can't be changed.
isOnDemand, // (error, inputs) => void
//
// Error callback will get called when async task get any errors.
onError,
// (inputs) => void
//
// Cancel callback will get called when async task is canceled.
onCancel,
// (result, inputs) => void
//
// Success callback will get called when async task is success.
onSuccess,
},
);
`Feature Comparison
| Feature | useAsync() | useAsync() (isOnDemand = true) |
| :--------------------------------- | :--------: | :----------------------------: |
| Execute on mount | ✓ | |
| Re-execute on inputs changes | ✓ | |
| Re-execute on-demand | ✓ | ✓ |
| Cancel on unmount | ✓ | ✓ |
| Cancel on re-execute | ✓ | ✓ |
| Cancel on-demand | ✓ | ✓ |
| Get notified when task is complete | ✓ | ✓ |
Migrating from v1 to v2
- Missing
useAsyncOnDemand() hook. Use useAsync() with isOnDemand = true.
- Missing injection as second argument of createTask(). See Providing custom cancellation if you want to cancel request using fetch() API. I drop this because of two reason. First, an async task can be anything, not only about data fetching. Provide injection for every calls can be annoying. Last, in v1, I supported fetch() API only, but in fact, any libraries can be used, such as axios, request... So it's not great. Instead, in v2, I provide you a convenient way to put any cancellation method to maximize usability.FAQ
$3
Let me show you two common use cases:
- Data fetching - Data should be fetched on the component gets mounted and inputs changes, such as apply filters using form. You also want to put a
Fetch button to let you fetch data on-demand whenever you want. In this case, you must use useAsync() hook with proactive mode.
- Click-to-action-button - You don't want any automatic mechanism. You want to click a button to do something, such as follow a person or you want to refetch data after you delete a data item. In this case, you must use useAsync() hook with on-demand mode.$3
Be sure
inputs doesn't change in every render. Understanding by examples:`jsx
function Example({ id }) {
// your inputs: [ { id } ]
// BUT { id } is always new in every render!
const task = useAsync(([{ id }]) => fetchSomethingById(id), [{ id }]);
}
`$3
If you use
useAsync() hook in proactive mode, be sure inputs changes and size of inputs must be the same between renders.`jsx
function Example({ ids }) {
// first render: ids = [1, 2, 3], inputs = [1, 2, 3]
// second render: ids = [1, 2], inputs = [1, 2]
// size changes => don't execute new async task!
const task = useAsync(ids => doSomething(ids), ids);
}
`If you use
useAsync() hook in on-demand mode, you must execute on-demand. See below for details.$3
Because of convenient. Sometimes you might want to write code like this and you don't expect re-execution happens:
`jsx
function Example({ id }) {
// createTask() changes every render!
const task = useAsync(([id]) => doSomething(id), [id]); // DO NOT use write this code!
// const task = useAsync(() => doSomething(id), []);
}
`Make
createTask() depends on inputs as param, move it out of React component if possible for clarification.$3
No execution at all. When you execute on-demand, the latest
inputs will be used to create a new async task.$3
Yes. Via
execute() function in useAsync() result.`jsx
function Example() {
const { execute } = useAsync(...);
}
`$3
Yes. An async task will be canceled before a new async task to be executed or when the component gets unmounted.
$3
Yes. Via
cancel() function in useAsync() result.`jsx
function Example({ id }) {
const { cancel } = useAsync(...);
}
``Nothing happens.
No matter how often callback changes, its version in the same execution render will be used.
- Website:
- Playground:
MIT