|
1 |
| -import { useCallback, useEffect, useRef, useState } from 'react'; |
| 1 | +import { useCallback, useMemo, useRef, useState } from 'react'; |
2 | 2 |
|
3 | 3 | import {
|
4 | 4 | ApiFn,
|
@@ -59,56 +59,42 @@ export function useAsyncResource<ResponseType, ArgTypes extends any[]>(
|
59 | 59 | apiFunction: ApiFn<ResponseType> | ApiFn<ResponseType, ArgTypes>,
|
60 | 60 | ...parameters: ArgTypes
|
61 | 61 | ) {
|
62 |
| - const firstRender = useRef(true); |
| 62 | + // keep the data reader inside a mutable object ref |
| 63 | + // always initialize with a lazy data reader, as it can be overwritten by the useMemo immediately |
| 64 | + const dataReaderObj = useRef<DataOrModifiedFn<ResponseType> | LazyDataOrModifiedFn<ResponseType>>(() => undefined); |
63 | 65 |
|
64 |
| - // initialize the data reader |
65 |
| - const [dataReader, updateDataReader] = useState(() => { |
66 |
| - // lazy initialization, when no parameters are passed |
67 |
| - if (!parameters.length) { |
68 |
| - // we return an empty data reader function |
69 |
| - return (() => undefined) as LazyDataOrModifiedFn<ResponseType>; |
70 |
| - } |
71 |
| - |
72 |
| - // eager initialization for api functions that don't accept arguments |
73 |
| - if ( |
74 |
| - // check that the api function doesn't take any arguments |
75 |
| - !apiFunction.length && |
76 |
| - // but the user passed an empty array as the only parameter |
77 |
| - parameters.length === 1 && |
78 |
| - Array.isArray(parameters[0]) && |
79 |
| - parameters[0].length === 0 |
80 |
| - ) { |
81 |
| - return initializeDataReader(apiFunction as ApiFn<ResponseType>); |
| 66 | + // like useEffect, but runs immediately |
| 67 | + useMemo(() => { |
| 68 | + if (parameters.length) { |
| 69 | + // eager initialization for api functions that don't accept arguments |
| 70 | + if ( |
| 71 | + // check that the api function doesn't take any arguments |
| 72 | + !apiFunction.length && |
| 73 | + // but the user passed an empty array as the only parameter |
| 74 | + parameters.length === 1 && |
| 75 | + Array.isArray(parameters[0]) && |
| 76 | + parameters[0].length === 0 |
| 77 | + ) { |
| 78 | + dataReaderObj.current = initializeDataReader(apiFunction as ApiFn<ResponseType>); |
| 79 | + } else { |
| 80 | + // eager initialization for all other cases |
| 81 | + dataReaderObj.current = initializeDataReader( |
| 82 | + apiFunction as ApiFn<ResponseType, ArgTypes>, |
| 83 | + ...parameters, |
| 84 | + ); |
| 85 | + } |
82 | 86 | }
|
| 87 | + }, [apiFunction, ...parameters]); |
83 | 88 |
|
84 |
| - // eager initialization for all other cases |
85 |
| - return initializeDataReader( |
86 |
| - apiFunction as ApiFn<ResponseType, ArgTypes>, |
87 |
| - ...parameters, |
88 |
| - ); |
89 |
| - }); |
| 89 | + // state to force re-render |
| 90 | + const [, forceRender] = useState(0); |
90 | 91 |
|
91 |
| - // the updater function |
92 |
| - const updater = useCallback( |
93 |
| - (...newParameters: ArgTypes) => { |
94 |
| - updateDataReader(() => |
95 |
| - initializeDataReader( |
96 |
| - apiFunction as ApiFn<ResponseType, ArgTypes>, |
97 |
| - ...newParameters, |
98 |
| - ), |
99 |
| - ); |
100 |
| - }, |
101 |
| - [apiFunction], |
102 |
| - ); |
103 |
| - |
104 |
| - // automatically call the updater function every time the params of the hook change |
105 |
| - useEffect(() => { |
106 |
| - if (firstRender.current) { |
107 |
| - firstRender.current = false; |
108 |
| - } else if (apiFunction.length > 0) { |
109 |
| - updater(...parameters); |
110 |
| - } |
111 |
| - }, [...parameters]); |
| 92 | + const updaterFn = useCallback((...newParameters: ArgTypes) => { |
| 93 | + // update the object ref |
| 94 | + dataReaderObj.current = initializeDataReader(apiFunction as ApiFn<ResponseType, ArgTypes>, ...newParameters); |
| 95 | + // update state to force a re-render |
| 96 | + forceRender(ct => 1 - ct); |
| 97 | + }, [apiFunction]); |
112 | 98 |
|
113 |
| - return [dataReader, updater]; |
| 99 | + return [dataReaderObj.current, updaterFn]; |
114 | 100 | }
|
0 commit comments