Skip to content

Commit fabbc25

Browse files
authored
Make sure the promise render prop is always defined (#148)
* Make sure the promise render prop is always a Promise. * Add a warning about providing a rejection handler.
1 parent 11d753c commit fabbc25

File tree

4 files changed

+22
-5
lines changed

4 files changed

+22
-5
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,9 @@ A reference to the internal wrapper promise created when starting a new promise
686686
`run` / `reload`). It fulfills or rejects along with the provided `promise` / `promiseFn` / `deferFn`. Useful as a
687687
chainable alternative to the `onResolve` / `onReject` callbacks.
688688

689+
Warning! If you chain on `promise`, you MUST provide a rejection handler (e.g. `.catch(...)`). Otherwise React will
690+
throw an exception and crash if the promise rejects.
691+
689692
#### `run`
690693

691694
> `function(...args: any[]): void`

packages/react-async/src/Async.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import React from "react"
33
import globalScope from "./globalScope"
44
import { IfInitial, IfPending, IfFulfilled, IfRejected, IfSettled } from "./helpers"
55
import propTypes from "./propTypes"
6-
import { actionTypes, init, dispatchMiddleware, reducer as asyncReducer } from "./reducer"
6+
import {
7+
neverSettle,
8+
actionTypes,
9+
init,
10+
dispatchMiddleware,
11+
reducer as asyncReducer,
12+
} from "./reducer"
713

814
/**
915
* createInstance allows you to create instances of Async that are bound to a specific promise.
@@ -32,7 +38,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
3238
this.mounted = false
3339
this.counter = 0
3440
this.args = []
35-
this.promise = undefined
41+
this.promise = neverSettle
3642
this.abortController = { abort: () => {} }
3743
this.state = {
3844
...init({ initialValue, promise, promiseFn }),

packages/react-async/src/reducer.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { getInitialStatus, getIdleStatus, getStatusProps, statusTypes } from "./status"
22

3+
export const neverSettle = new Promise(() => {})
4+
35
export const actionTypes = {
46
start: "start",
57
cancel: "cancel",
@@ -16,7 +18,7 @@ export const init = ({ initialValue, promise, promiseFn }) => ({
1618
finishedAt: initialValue ? new Date() : undefined,
1719
...getStatusProps(getInitialStatus(initialValue, promise || promiseFn)),
1820
counter: 0,
19-
promise: undefined,
21+
promise: neverSettle,
2022
})
2123

2224
export const reducer = (state, { type, payload, meta }) => {

packages/react-async/src/useAsync.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { useCallback, useDebugValue, useEffect, useMemo, useRef, useReducer } from "react"
22

33
import globalScope from "./globalScope"
4-
import { actionTypes, init, dispatchMiddleware, reducer as asyncReducer } from "./reducer"
4+
import {
5+
neverSettle,
6+
actionTypes,
7+
init,
8+
dispatchMiddleware,
9+
reducer as asyncReducer,
10+
} from "./reducer"
511

612
const noop = () => {}
713

@@ -12,7 +18,7 @@ const useAsync = (arg1, arg2) => {
1218
const isMounted = useRef(true)
1319
const lastArgs = useRef(undefined)
1420
const lastOptions = useRef(undefined)
15-
const lastPromise = useRef(undefined)
21+
const lastPromise = useRef(neverSettle)
1622
const abortController = useRef({ abort: noop })
1723

1824
const { devToolsDispatcher } = globalScope.__REACT_ASYNC__

0 commit comments

Comments
 (0)