react-async-hooks v1.0.4
React hooks for promises. There are three hooks:
useAsync: This is likeuseMemooruseEffect.useAsyncCallback: This is likeuseCallback.useAsyncVoidCallback. This is also likeuseCallbackbut it doesn't return or throw.
All hooks support aborting using an
AbortSignal.
useAsync
Basic
import { useAsync } from 'react-async-hooks';
const fetchMessage = async id => {
const response = await fetch(`https://example.com/${id}`);
return await response.text();
};
const Message = props => {
const { state, reason, value } = useAsync(() => fetchMessage(props.id), [props.id]);
if (state === 'pending') {
return 'Loading';
}
if (reason) {
return 'Error';
}
return value;
};fetchMessage will be called when props.id changes. state will be 'pending', 'rejected' or
'fulfilled'. value will be defined if state is 'fulfilled'. reason will be defined if
state is 'rejected'.
Aborting
import { useAsync } from 'react-async-hooks';
const fetchMessage = async (id, signal) => {
const response = await fetch(`https://example.com/${id}`, { signal });
return await response.text();
};
const Message = props => {
const { state, reason, value } = useAsync(
({ signal }) => fetchMessage(props.id, signal),
[props.id],
);
if (state === 'pending') {
return 'Loading';
}
if (reason) {
return 'Error';
}
return value;
};signal is an AbortSignal. Old
signals will be aborted when props.id changes or component unmounts. Values returned and errors
thrown after aborting will be ignored.
Keeping state as 'pending'
import { useAsync } from 'react-async-hooks';
const fetchMessage = async id => {
const response = await fetch(`https://example.com/${id}`);
return await response.text();
};
const Message = props => {
const { state, reason, value } = useAsync(
async ({ pending }) => {
if (props.id === undefined) {
return pending;
}
return await fetchMessage(props.id);
},
[props.id],
);
if (state === 'pending') {
return 'Loading';
}
if (reason) {
return 'Error';
}
return value;
};state will stay as 'pending' till props.id is undefined.
useAsyncCallback
import { useAsyncCallback } from 'react-async-hooks';
const fetchMessage = async (id, signal) => {
const response = await fetch(`https://example.com/${id}`, { signal });
return await response.text();
};
const Message = props => {
const [callback, { state, reason, value }] = useAsyncCallback(
async ({ signal }) => {
try {
return await fetchMessage(props.id);
} catch (error) {
if (!signal.aborted) {
throw error;
}
}
},
[props.id],
);
let message;
if (state === 'pending') {
message = 'Loading';
} else if (reason) {
message = 'Error';
} else {
message = value;
}
return (
<div>
<button onClick={callback}>Update message</button>
<div>{message}</div>
</div>
);
};fetchMessage will be called when button is clicked. If an error is thrown inside
useAsyncCallback then callback will reject with the same error. If a value is returned inside
useAsyncCallback then callback will fulfill with the same value.
useAsyncVoidCallback
import { useAsyncVoidCallback } from 'react-async-hooks';
const fetchMessage = async (id, signal) => {
const response = await fetch(`https://example.com/${id}`, { signal });
return await response.text();
};
const Message = props => {
const [callback, { state, reason, value }] = useAsyncVoidCallback(
async ({ pending, signal }) => {
if (props.id === undefined) {
return pending;
}
return await fetchMessage(props.id);
},
[props.id],
);
let message;
if (state === 'pending') {
message = 'Loading';
} else if (reason) {
message = 'Error';
} else {
message = value;
}
return (
<div>
<button onClick={callback}>Update message</button>
<div>{message}</div>
</div>
);
};fetchMessage will be called when button is clicked. callback will never reject and will always
be fulfilled with void.
Race conditions
Wrong:
import { useState } from 'react';
import { useAsync } from 'react-async-hooks';
const fetchMessage = async id => {
const response = await fetch(`https://example.com/${id}`);
return await response.text();
};
const Message = props => {
const [message, setMessage] = useState();
useAsync(async () => {
const message = await fetchMessage(props.id);
setMessage(message);
}, [props.id]);
if (state === 'pending') {
return 'Loading';
}
if (reason) {
return 'Error';
}
return value;
};Here it's possible that an old fetchMessage call resolves after a new fetchMessage call. This is
handled automatically in the value returned by useAsync but if state is changed inside the
callback passed to useAsync or useAsyncCallback then signal.aborted should be checked before
changing state.
Correct:
import { useState } from 'react';
import { useAsync } from 'react-async-hooks';
const fetchMessage = async id => {
const response = await fetch(`https://example.com/${id}`);
return await response.text();
};
const Message = props => {
const [message, setMessage] = useState();
useAsync(
async ({ signal }) => {
const message = await fetchMessage(props.id);
if (!signal.aborted) {
setMessage(message);
}
},
[props.id],
);
if (state === 'pending') {
return 'Loading';
}
if (reason) {
return 'Error';
}
return value;
};API
Common
type UseAsyncPendingResult = {
state: 'pending';
};
type UseAsyncRejectedResult = {
state: 'rejected';
reason: Reason;
};
type UseAsyncFulfilledResult = {
state: 'fulfilled';
value: Value;
};
type UseAsyncResult = UseAsyncPendingResult | UseAsyncRejectedResult | UseAsyncFulfilledResult;useAsync
function useAsync(
fn: (options: { pending: Pending; signal: AbortSignal }) => Promise<Value | Pending>,
deps: any[],
): UseAsyncResult;useAsyncCallback
type Callback = (...args: Args) => Promise<Value>;
function useAsyncCallback(
fn: (options: { signal: AbortSignal }, ...args: Args) => Promise<Value>,
deps: any[],
): [Callback, UseAsyncResult];useAsyncVoidCallback
type Callback = (...args: Args) => Promise<void>;
function useAsyncVoidCallback(
fn: (
options: { pending: Pending; signal: AbortSignal },
...args: Args
) => Promise<Value | Pending>,
deps: any[],
): [Callback, UseAsyncResult];ESLint
Use additionalHooks option of
eslint-plugin-react-hooks to check for
incorrect dependencies.
{
"rules": {
"react-hooks/exhaustive-deps": [
"error",
{
"additionalHooks": "(useAsync|useAsyncCallback|useAsyncVoidCallback)"
}
]
}
}1 year ago
1 year ago
2 years ago
5 years ago
5 years ago
5 years ago
5 years ago