react-async-hooks v1.0.4
React hooks for promises. There are three hooks:
useAsync
: This is likeuseMemo
oruseEffect
.useAsyncCallback
: This is likeuseCallback
.useAsyncVoidCallback
. This is also likeuseCallback
but 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
4 years ago
4 years ago
4 years ago
4 years ago