Skip to content

Commit b4c5ece

Browse files
committed
Make sure 'run' always passes the latest props/options, regardless of memoization. Fixes #69.
1 parent f22acd0 commit b4c5ece

File tree

2 files changed

+39
-5
lines changed

2 files changed

+39
-5
lines changed

packages/react-async/src/specs.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,40 @@ 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+
const Parent = () => {
413+
const [count, setCount] = React.useState(0)
414+
return (
415+
<>
416+
<button onClick={() => setCount(count + 1)}>inc</button>
417+
{count && <Child count={count} />}
418+
</>
419+
)
420+
}
421+
const { getByText, getByTestId } = render(<Parent />)
422+
fireEvent.click(getByText("inc"))
423+
expect(getByTestId("counter")).toHaveTextContent("1")
424+
fireEvent.click(getByText("inc"))
425+
expect(getByTestId("counter")).toHaveTextContent("2")
426+
fireEvent.click(getByText("run"))
427+
expect(deferFn).toHaveBeenCalledWith(
428+
[2],
429+
expect.objectContaining({ count: 2, deferFn }),
430+
abortCtrl
431+
)
432+
})
433+
400434
test("`reload` uses the arguments of the previous run", () => {
401435
let counter = 1
402436
const deferFn = jest.fn().mockReturnValue(resolveTo())

packages/react-async/src/useAsync.js

Lines changed: 5 additions & 5 deletions
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)