Skip to content

Commit cf5ff97

Browse files
committed
Add useAsync.
1 parent 54be0ae commit cf5ff97

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from "react"
2+
export { default as useAsync } from "./useAsync"
23

34
const isFunction = arg => typeof arg === "function"
45

src/useAsync.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { useState, useEffect, useMemo } from "react"
2+
3+
const useAsync = props => {
4+
let counter = 0
5+
let isMounted = false
6+
let lastArgs = undefined
7+
8+
const initialError = props.initialValue instanceof Error ? props.initialValue : undefined
9+
const initialData = initialError ? undefined : props.initialValue
10+
const [data, setData] = useState(initialData)
11+
const [error, setError] = useState(initialError)
12+
const [startedAt, setStartedAt] = useState(props.promiseFn ? new Date() : undefined)
13+
const [finishedAt, setFinishedAt] = useState(props.initialValue ? new Date() : undefined)
14+
15+
const cancel = () => {
16+
counter++
17+
setStartedAt(undefined)
18+
}
19+
20+
const start = () => {
21+
counter++
22+
setStartedAt(new Date())
23+
setFinishedAt(undefined)
24+
}
25+
26+
const end = () => setFinishedAt(new Date())
27+
28+
const handleData = (data, callback = () => {}) => {
29+
if (isMounted) {
30+
end()
31+
setData(data)
32+
setError(undefined)
33+
callback(data)
34+
}
35+
return data
36+
}
37+
38+
const handleError = (error, callback = () => {}) => {
39+
if (isMounted) {
40+
end()
41+
setError(error)
42+
callback(error)
43+
}
44+
return error
45+
}
46+
47+
const onResolve = count => data => count === counter && handleData(data, props.onResolve)
48+
const onReject = count => error => count === counter && handleError(error, props.onReject)
49+
50+
const load = () => {
51+
if (props.promiseFn) {
52+
start()
53+
props.promiseFn(props).then(onResolve(counter), onReject(counter))
54+
}
55+
}
56+
57+
const run = (...args) => {
58+
if (props.deferFn) {
59+
lastArgs = args
60+
start()
61+
return props.deferFn(...args, props).then(onResolve(counter), onReject(counter))
62+
}
63+
}
64+
65+
const reload = () => (lastArgs ? run(...lastArgs) : load())
66+
67+
useEffect(() => {
68+
isMounted = true
69+
return () => (isMounted = false)
70+
}, [])
71+
72+
useEffect(load, [props.promiseFn, props.watch])
73+
74+
return useMemo(
75+
() => ({
76+
isLoading: startedAt && (!finishedAt || finishedAt < startedAt),
77+
startedAt,
78+
finishedAt,
79+
data,
80+
error,
81+
cancel,
82+
run,
83+
reload,
84+
setData: handleData,
85+
setError: handleError,
86+
initialValue: props.initialValue
87+
}),
88+
[data, error, startedAt, finishedAt]
89+
)
90+
}
91+
92+
const unsupported = () => {
93+
throw new Error("useAsync requires [email protected] or later")
94+
}
95+
96+
export default (useState ? useAsync : unsupported)

0 commit comments

Comments
 (0)