Skip to content

Commit d8a8245

Browse files
committed
Update useAsync to use useRef for instance variables.
1 parent 4e4d1d7 commit d8a8245

File tree

4 files changed

+356
-69
lines changed

4 files changed

+356
-69
lines changed

package-lock.json

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/spec.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import "jest-dom/extend-expect"
22
import React from "react"
33
import { render, fireEvent, cleanup, waitForElement } from "react-testing-library"
4-
import Async, { createInstance, useAsync } from "./"
4+
import Async, { createInstance } from "./"
55

66
afterEach(cleanup)
77

@@ -423,10 +423,3 @@ test("an unrelated change in props does not update the Context", async () => {
423423
)
424424
expect(one).toBe(two)
425425
})
426-
427-
test("useAsync returns render props", async () => {
428-
const promiseFn = () => new Promise(resolve => setTimeout(resolve, 0, "done"))
429-
const Async = ({ children, ...props }) => children(useAsync(props))
430-
const { getByText } = render(<Async promiseFn={promiseFn}>{({ data }) => data || null}</Async>)
431-
await waitForElement(() => getByText("done"))
432-
})

src/useAsync.js

Lines changed: 39 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,81 @@
1-
import { useState, useEffect, useMemo } from "react"
1+
import { useState, useEffect, useMemo, useRef } from "react"
22

33
const useAsync = (opts, init) => {
4-
let counter = 0
5-
let isMounted = false
6-
let lastArgs = undefined
4+
const counter = useRef(0)
5+
const isMounted = useRef(true)
6+
const lastArgs = useRef(undefined)
77

88
const options = typeof opts === "function" ? { promiseFn: opts, initialValue: init } : opts
99
const { promiseFn, deferFn, initialValue, onResolve, onReject, watch } = options
1010

11-
const [data, setData] = useState(initialValue instanceof Error ? undefined : initialValue)
12-
const [error, setError] = useState(initialValue instanceof Error ? initialValue : undefined)
13-
const [startedAt, setStartedAt] = useState(promiseFn ? new Date() : undefined)
14-
const [finishedAt, setFinishedAt] = useState(initialValue ? new Date() : undefined)
15-
16-
const cancel = () => {
17-
counter++
18-
setStartedAt(undefined)
19-
}
20-
21-
const start = () => {
22-
counter++
23-
setStartedAt(new Date())
24-
setFinishedAt(undefined)
25-
}
26-
27-
const end = () => setFinishedAt(new Date())
11+
const [state, setState] = useState({
12+
data: initialValue instanceof Error ? undefined : initialValue,
13+
error: initialValue instanceof Error ? initialValue : undefined,
14+
startedAt: promiseFn ? new Date() : undefined,
15+
finishedAt: initialValue ? new Date() : undefined,
16+
})
2817

2918
const handleData = (data, callback = () => {}) => {
30-
if (isMounted) {
31-
end()
32-
setData(data)
33-
setError(undefined)
19+
if (isMounted.current) {
20+
setState(state => ({ ...state, data, error: undefined, finishedAt: new Date() }))
3421
callback(data)
3522
}
3623
return data
3724
}
3825

3926
const handleError = (error, callback = () => {}) => {
40-
if (isMounted) {
41-
end()
42-
setError(error)
27+
if (isMounted.current) {
28+
setState(state => ({ ...state, error, finishedAt: new Date() }))
4329
callback(error)
4430
}
4531
return error
4632
}
4733

48-
const handleResolve = count => data => count === counter && handleData(data, onResolve)
49-
const handleReject = count => error => count === counter && handleError(error, onReject)
34+
const handleResolve = count => data => count === counter.current && handleData(data, onResolve)
35+
const handleReject = count => error => count === counter.current && handleError(error, onReject)
36+
37+
const start = () => {
38+
counter.current++
39+
setState(state => ({
40+
...state,
41+
startedAt: new Date(),
42+
finishedAt: undefined,
43+
}))
44+
}
5045

5146
const load = () => {
52-
if (promiseFn) {
47+
if (promiseFn && !(initialValue && counter.current === 0)) {
5348
start()
54-
promiseFn(options).then(handleResolve(counter), handleReject(counter))
49+
promiseFn(options).then(handleResolve(counter.current), handleReject(counter.current))
5550
}
5651
}
5752

5853
const run = (...args) => {
5954
if (deferFn) {
60-
lastArgs = args
6155
start()
62-
return deferFn(...args, options).then(handleResolve(counter), handleReject(counter))
56+
lastArgs.current = args
57+
return deferFn(...args, options).then(handleResolve(counter.current), handleReject(counter.current))
6358
}
6459
}
6560

66-
const reload = () => (lastArgs ? run(...lastArgs) : load())
67-
68-
useEffect(() => {
69-
isMounted = true
70-
return () => (isMounted = false)
71-
}, [])
72-
7361
useEffect(load, [promiseFn, watch])
62+
useEffect(() => () => (isMounted.current = false), [])
7463

7564
return useMemo(
7665
() => ({
77-
isLoading: startedAt && (!finishedAt || finishedAt < startedAt),
78-
startedAt,
79-
finishedAt,
80-
data,
81-
error,
66+
...state,
67+
isLoading: state.startedAt && (!state.finishedAt || state.finishedAt < state.startedAt),
8268
initialValue,
83-
cancel,
8469
run,
85-
reload,
70+
reload: () => (lastArgs ? run(...lastArgs) : load()),
71+
cancel: () => {
72+
counter.current++
73+
setState(state => ({ ...state, startedAt: undefined }))
74+
},
8675
setData: handleData,
87-
setError: handleError
76+
setError: handleError,
8877
}),
89-
[data, error, startedAt, finishedAt]
78+
[state]
9079
)
9180
}
9281

0 commit comments

Comments
 (0)