Skip to content

Commit 1e726ef

Browse files
committed
Use shared reducer for core state logic.
1 parent 409d003 commit 1e726ef

File tree

3 files changed

+82
-49
lines changed

3 files changed

+82
-49
lines changed

src/Async.js

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from "react"
2+
import { actions, init, reducer } from "./reducer"
23

34
let PropTypes
45
try {
@@ -30,21 +31,13 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
3031
const promise = props.promise
3132
const promiseFn = props.promiseFn || defaultProps.promiseFn
3233
const initialValue = props.initialValue || defaultProps.initialValue
33-
const initialError = initialValue instanceof Error ? initialValue : undefined
34-
const initialData = initialError ? undefined : initialValue
3534

3635
this.mounted = false
3736
this.counter = 0
3837
this.args = []
3938
this.abortController = { abort: () => {} }
4039
this.state = {
41-
initialValue,
42-
data: initialData,
43-
error: initialError,
44-
isLoading: !!promise || (promiseFn && !initialValue),
45-
startedAt: promise ? new Date() : undefined,
46-
finishedAt: initialValue ? new Date() : undefined,
47-
counter: this.counter,
40+
...init({ initialValue, promise, promiseFn }),
4841
cancel: this.cancel,
4942
run: this.run,
5043
reload: () => {
@@ -54,6 +47,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
5447
setData: this.setData,
5548
setError: this.setError,
5649
}
50+
this.dispatch = (action, callback) => this.setState(state => reducer(state, action), callback)
5751
}
5852

5953
componentDidMount() {
@@ -89,12 +83,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
8983
this.abortController = new window.AbortController()
9084
}
9185
this.counter++
92-
this.setState({
93-
isLoading: true,
94-
startedAt: new Date(),
95-
finishedAt: undefined,
96-
counter: this.counter,
97-
})
86+
this.dispatch({ type: actions.start, meta: { counter: this.counter } })
9887
}
9988

10089
load() {
@@ -129,12 +118,12 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
129118
cancel() {
130119
this.counter++
131120
this.abortController.abort()
132-
this.setState({ isLoading: false, startedAt: undefined, counter: this.counter })
121+
this.dispatch({ type: actions.cancel, meta: { counter: this.counter } })
133122
}
134123

135124
onResolve(counter) {
136125
return data => {
137-
if (this.mounted && this.counter === counter) {
126+
if (this.counter === counter) {
138127
const onResolve = this.props.onResolve || defaultProps.onResolve
139128
this.setData(data, () => onResolve && onResolve(data))
140129
}
@@ -144,7 +133,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
144133

145134
onReject(counter) {
146135
return error => {
147-
if (this.mounted && this.counter === counter) {
136+
if (this.counter === counter) {
148137
const onReject = this.props.onReject || defaultProps.onReject
149138
this.setError(error, () => onReject && onReject(error))
150139
}
@@ -153,12 +142,12 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
153142
}
154143

155144
setData(data, callback) {
156-
this.setState({ data, error: undefined, isLoading: false, finishedAt: new Date() }, callback)
145+
this.mounted && this.dispatch({ type: actions.fulfill, payload: data }, callback)
157146
return data
158147
}
159148

160149
setError(error, callback) {
161-
this.setState({ error, isLoading: false, finishedAt: new Date() }, callback)
150+
this.mounted && this.dispatch({ type: actions.reject, payload: error, error: true }, callback)
162151
return error
163152
}
164153

src/reducer.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
export const actions = {
2+
start: "start",
3+
cancel: "cancel",
4+
fulfill: "fulfill",
5+
reject: "reject",
6+
}
7+
8+
export const init = ({ initialValue, promise, promiseFn }) => ({
9+
initialValue,
10+
data: initialValue instanceof Error ? undefined : initialValue,
11+
error: initialValue instanceof Error ? initialValue : undefined,
12+
isLoading: !!promise || (promiseFn && !initialValue),
13+
startedAt: promise || promiseFn ? new Date() : undefined,
14+
finishedAt: initialValue ? new Date() : undefined,
15+
counter: 0,
16+
})
17+
18+
export const reducer = (state, { type, payload, meta }) => {
19+
switch (type) {
20+
case actions.start:
21+
return {
22+
...state,
23+
isLoading: true,
24+
startedAt: new Date(),
25+
finishedAt: undefined,
26+
counter: meta.counter,
27+
}
28+
case actions.cancel:
29+
return {
30+
...state,
31+
isLoading: false,
32+
startedAt: undefined,
33+
counter: meta.counter,
34+
}
35+
case actions.fulfill:
36+
return {
37+
...state,
38+
data: payload,
39+
error: undefined,
40+
isLoading: false,
41+
finishedAt: new Date(),
42+
}
43+
case actions.reject:
44+
return {
45+
...state,
46+
error: payload,
47+
isLoading: false,
48+
finishedAt: new Date(),
49+
}
50+
}
51+
}

src/useAsync.js

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,48 @@
1-
import { useCallback, useDebugValue, useEffect, useMemo, useRef, useState } from "react"
1+
import { useCallback, useDebugValue, useEffect, useMemo, useRef, useReducer } from "react"
2+
import { actions, init, reducer } from "./reducer"
3+
4+
const noop = () => {}
25

36
const useAsync = (arg1, arg2) => {
47
const counter = useRef(0)
58
const isMounted = useRef(true)
69
const lastArgs = useRef(undefined)
710
const prevOptions = useRef(undefined)
8-
const abortController = useRef({ abort: () => {} })
11+
const abortController = useRef({ abort: noop })
912

1013
const options = typeof arg1 === "function" ? { ...arg2, promiseFn: arg1 } : arg1
1114
const { promise, promiseFn, deferFn, initialValue, onResolve, onReject, watch, watchFn } = options
1215

13-
const [state, setState] = useState({
14-
data: initialValue instanceof Error ? undefined : initialValue,
15-
error: initialValue instanceof Error ? initialValue : undefined,
16-
startedAt: promise || promiseFn ? new Date() : undefined,
17-
finishedAt: initialValue ? new Date() : undefined,
18-
})
16+
const [state, dispatch] = useReducer(reducer, options, init)
1917

20-
const handleData = (data, callback = () => {}) => {
18+
const setData = (data, callback = noop) => {
2119
if (isMounted.current) {
22-
setState(state => ({ ...state, data, error: undefined, finishedAt: new Date() }))
23-
callback(data)
20+
dispatch({ type: actions.fulfill, payload: data })
21+
callback()
2422
}
2523
return data
2624
}
2725

28-
const handleError = (error, callback = () => {}) => {
26+
const setError = (error, callback = noop) => {
2927
if (isMounted.current) {
30-
setState(state => ({ ...state, error, finishedAt: new Date() }))
31-
callback(error)
28+
dispatch({ type: actions.reject, payload: error, error: true })
29+
callback()
3230
}
3331
return error
3432
}
3533

36-
const handleResolve = count => data => count === counter.current && handleData(data, onResolve)
37-
const handleReject = count => error => count === counter.current && handleError(error, onReject)
34+
const handleResolve = count => data =>
35+
count === counter.current && setData(data, () => onResolve && onResolve(data))
36+
const handleReject = count => error =>
37+
count === counter.current && setError(error, () => onReject && onReject(error))
3838

3939
const start = () => {
4040
if ("AbortController" in window) {
4141
abortController.current.abort()
4242
abortController.current = new window.AbortController()
4343
}
4444
counter.current++
45-
setState(state => ({
46-
...state,
47-
startedAt: new Date(),
48-
finishedAt: undefined,
49-
}))
45+
dispatch({ type: actions.start, meta: { counter: counter.current } })
5046
}
5147

5248
const load = () => {
@@ -79,7 +75,7 @@ const useAsync = (arg1, arg2) => {
7975
const cancel = () => {
8076
counter.current++
8177
abortController.current.abort()
82-
setState(state => ({ ...state, startedAt: undefined }))
78+
dispatch({ type: actions.cancel, meta: { counter: counter.current } })
8379
}
8480

8581
useEffect(() => {
@@ -104,14 +100,11 @@ const useAsync = (arg1, arg2) => {
104100
return useMemo(
105101
() => ({
106102
...state,
107-
initialValue,
108-
isLoading: state.startedAt && (!state.finishedAt || state.finishedAt < state.startedAt),
109-
counter: counter.current,
110103
run,
111104
reload: () => (lastArgs.current ? run(...lastArgs.current) : load()),
112105
cancel,
113-
setData: handleData,
114-
setError: handleError,
106+
setData,
107+
setError,
115108
}),
116109
[state]
117110
)
@@ -152,5 +145,5 @@ const unsupported = () => {
152145
)
153146
}
154147

155-
export default (useState ? useAsync : unsupported)
156-
export const useFetch = useState ? useAsyncFetch : unsupported
148+
export default (useEffect ? useAsync : unsupported)
149+
export const useFetch = useEffect ? useAsyncFetch : unsupported

0 commit comments

Comments
 (0)