Skip to content

Commit 0da6eb8

Browse files
authored
Make sure 'run' always passes the latest props/options (#71)
* Make sure 'run' always passes the latest props/options, regardless of memoization. Fixes #69. * Tests must be compatible with React v16.3.
1 parent f22acd0 commit 0da6eb8

File tree

2 files changed

+45
-5
lines changed

2 files changed

+45
-5
lines changed

packages/react-async/src/specs.js

+40
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,46 @@ export const withDeferFn = (Async, abortCtrl) => () => {
397397
expect(deferFn).toHaveBeenCalledWith(["go", 2], expect.objectContaining(props), abortCtrl)
398398
})
399399

400+
test("always passes the latest props", async () => {
401+
const deferFn = jest.fn().mockReturnValue(resolveTo())
402+
const Child = ({ count }) => (
403+
<Async deferFn={deferFn} count={count}>
404+
{({ run }) => (
405+
<>
406+
<button onClick={() => run(count)}>run</button>
407+
<div data-testid="counter">{count}</div>
408+
</>
409+
)}
410+
</Async>
411+
)
412+
class Parent extends React.Component {
413+
constructor(props) {
414+
super(props)
415+
this.state = { count: 0 }
416+
}
417+
render() {
418+
const inc = () => this.setState(state => ({ count: state.count + 1 }))
419+
return (
420+
<>
421+
<button onClick={inc}>inc</button>
422+
{this.state.count && <Child count={this.state.count} />}
423+
</>
424+
)
425+
}
426+
}
427+
const { getByText, getByTestId } = render(<Parent />)
428+
fireEvent.click(getByText("inc"))
429+
expect(getByTestId("counter")).toHaveTextContent("1")
430+
fireEvent.click(getByText("inc"))
431+
expect(getByTestId("counter")).toHaveTextContent("2")
432+
fireEvent.click(getByText("run"))
433+
expect(deferFn).toHaveBeenCalledWith(
434+
[2],
435+
expect.objectContaining({ count: 2, deferFn }),
436+
abortCtrl
437+
)
438+
})
439+
400440
test("`reload` uses the arguments of the previous run", () => {
401441
let counter = 1
402442
const deferFn = jest.fn().mockReturnValue(resolveTo())

packages/react-async/src/useAsync.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const useAsync = (arg1, arg2) => {
1111
const counter = useRef(0)
1212
const isMounted = useRef(true)
1313
const lastArgs = useRef(undefined)
14-
const prevOptions = useRef(undefined)
14+
const lastOptions = useRef(undefined)
1515
const abortController = useRef({ abort: noop })
1616

1717
const { devToolsDispatcher } = globalScope.__REACT_ASYNC__
@@ -72,7 +72,7 @@ const useAsync = (arg1, arg2) => {
7272
}
7373
const isPreInitialized = initialValue && counter.current === 0
7474
if (promiseFn && !isPreInitialized) {
75-
return start(() => promiseFn(options, abortController.current)).then(
75+
return start(() => promiseFn(lastOptions.current, abortController.current)).then(
7676
handleResolve(counter.current),
7777
handleReject(counter.current)
7878
)
@@ -83,7 +83,7 @@ const useAsync = (arg1, arg2) => {
8383
const run = (...args) => {
8484
if (deferFn) {
8585
lastArgs.current = args
86-
return start(() => deferFn(args, options, abortController.current)).then(
86+
return start(() => deferFn(args, lastOptions.current, abortController.current)).then(
8787
handleResolve(counter.current),
8888
handleReject(counter.current)
8989
)
@@ -99,15 +99,15 @@ const useAsync = (arg1, arg2) => {
9999

100100
const { watch, watchFn } = options
101101
useEffect(() => {
102-
if (watchFn && prevOptions.current && watchFn(options, prevOptions.current)) load()
102+
if (watchFn && lastOptions.current && watchFn(options, lastOptions.current)) load()
103103
})
104+
useEffect(() => (lastOptions.current = options) && undefined)
104105
useEffect(() => {
105106
if (counter.current) cancel()
106107
if (promise || promiseFn) load()
107108
}, [promise, promiseFn, watch])
108109
useEffect(() => () => (isMounted.current = false), [])
109110
useEffect(() => () => cancel(), [])
110-
useEffect(() => (prevOptions.current = options) && undefined)
111111

112112
useDebugValue(state, ({ status }) => `[${counter.current}] ${status}`)
113113

0 commit comments

Comments
 (0)