@croct/plug-react 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,11 +16,8 @@ describe('useLoader', () => {
16
16
 
17
17
  beforeEach(() => {
18
18
  cacheKey.next();
19
- });
20
-
21
- afterEach(() => {
22
- jest.clearAllTimers();
23
19
  jest.resetAllMocks();
20
+ jest.clearAllTimers();
24
21
  });
25
22
 
26
23
  // Needed to use fake timers and promises:
@@ -212,6 +209,96 @@ describe('useLoader', () => {
212
209
  expect(loader).toHaveBeenCalledTimes(1);
213
210
  });
214
211
 
212
+ it('should reload the value when the cache key changes without initial value', async () => {
213
+ jest.useFakeTimers();
214
+
215
+ const loader = jest.fn()
216
+ .mockResolvedValueOnce('foo')
217
+ .mockImplementationOnce(
218
+ () => new Promise(resolve => {
219
+ setTimeout(() => resolve('bar'), 10);
220
+ }),
221
+ );
222
+
223
+ const {result, rerender} = renderHook<any, {initial: string}>(
224
+ props => useLoader({
225
+ cacheKey: cacheKey.current(),
226
+ loader: loader,
227
+ initial: props?.initial,
228
+ }),
229
+ );
230
+
231
+ await act(flushPromises);
232
+
233
+ rerender();
234
+
235
+ await waitFor(() => expect(result.current).toBe('foo'));
236
+
237
+ expect(loader).toHaveBeenCalledTimes(1);
238
+
239
+ cacheKey.next();
240
+
241
+ rerender({initial: 'loading'});
242
+
243
+ await waitFor(() => expect(result.current).toBe('loading'));
244
+
245
+ jest.advanceTimersByTime(10);
246
+
247
+ await waitFor(() => expect(result.current).toBe('bar'));
248
+
249
+ expect(loader).toHaveBeenCalledTimes(2);
250
+ });
251
+
252
+ it('should reload the value when the cache key changes with initial value', async () => {
253
+ jest.useFakeTimers();
254
+
255
+ const loader = jest.fn()
256
+ .mockImplementationOnce(
257
+ () => new Promise(resolve => {
258
+ setTimeout(() => resolve('foo'), 10);
259
+ }),
260
+ )
261
+ .mockImplementationOnce(
262
+ () => new Promise(resolve => {
263
+ setTimeout(() => resolve('bar'), 10);
264
+ }),
265
+ );
266
+
267
+ const {result, rerender} = renderHook<any, {initial: string}>(
268
+ props => useLoader({
269
+ cacheKey: cacheKey.current(),
270
+ initial: props?.initial ?? 'first content',
271
+ loader: loader,
272
+ }),
273
+ );
274
+
275
+ await act(flushPromises);
276
+
277
+ expect(result.current).toBe('first content');
278
+
279
+ jest.advanceTimersByTime(10);
280
+
281
+ await act(flushPromises);
282
+
283
+ await waitFor(() => expect(result.current).toBe('foo'));
284
+
285
+ expect(loader).toHaveBeenCalledTimes(1);
286
+
287
+ cacheKey.next();
288
+
289
+ rerender({initial: 'second content'});
290
+
291
+ await waitFor(() => expect(result.current).toBe('second content'));
292
+
293
+ jest.advanceTimersByTime(10);
294
+
295
+ await act(flushPromises);
296
+
297
+ await waitFor(() => expect(result.current).toBe('bar'));
298
+
299
+ expect(loader).toHaveBeenCalledTimes(2);
300
+ });
301
+
215
302
  it.each<[number, number|undefined]>(
216
303
  [
217
304
  // [Expected elapsed time, Expiration]
@@ -1,4 +1,4 @@
1
- import {useEffect, useRef, useState} from 'react';
1
+ import {useCallback, useEffect, useRef, useState} from 'react';
2
2
  import {Cache, EntryOptions} from './Cache';
3
3
 
4
4
  const cache = new Cache(60 * 1000);
@@ -8,38 +8,60 @@ export type CacheOptions<R> = EntryOptions<R> & {
8
8
  };
9
9
 
10
10
  export function useLoader<R>({initial, ...options}: CacheOptions<R>): R {
11
- const loadedValue: R|undefined = cache.get<R>(options.cacheKey)?.result;
11
+ const {cacheKey} = options;
12
+ const loadedValue: R|undefined = cache.get<R>(cacheKey)?.result;
12
13
  const [value, setValue] = useState(loadedValue !== undefined ? loadedValue : initial);
13
14
  const mountedRef = useRef(true);
14
- const optionsRef = useRef(initial !== undefined ? options : undefined);
15
+ const initialRef = useRef(initial);
16
+ const previousCacheKey = useRef(cacheKey);
15
17
 
16
- useEffect(
17
- () => {
18
- if (optionsRef.current !== undefined) {
19
- try {
20
- setValue(cache.load(optionsRef.current));
21
- } catch (result: unknown) {
22
- if (result instanceof Promise) {
23
- result.then((resolvedValue: R) => {
24
- if (mountedRef.current) {
25
- setValue(resolvedValue);
26
- }
27
- });
28
-
29
- return;
18
+ const load = useStableCallback(() => {
19
+ try {
20
+ setValue(cache.load(options));
21
+ } catch (result: unknown) {
22
+ if (result instanceof Promise) {
23
+ result.then((resolvedValue: R) => {
24
+ if (mountedRef.current) {
25
+ setValue(resolvedValue);
30
26
  }
27
+ });
28
+
29
+ return;
30
+ }
31
+
32
+ setValue(undefined);
33
+ }
34
+ });
31
35
 
32
- setValue(undefined);
36
+ const reset = useStableCallback(() => {
37
+ const newLoadedValue: R|undefined = cache.get<R>(cacheKey)?.result;
38
+
39
+ setValue(newLoadedValue !== undefined ? newLoadedValue : initial);
40
+
41
+ load();
42
+ });
43
+
44
+ useEffect(
45
+ () => {
46
+ if (previousCacheKey.current !== cacheKey) {
47
+ reset();
48
+ previousCacheKey.current = cacheKey;
49
+ }
50
+ },
51
+ [reset, cacheKey],
52
+ );
33
53
 
34
- return;
35
- }
54
+ useEffect(
55
+ () => {
56
+ if (initialRef.current !== undefined) {
57
+ load();
36
58
  }
37
59
 
38
60
  return () => {
39
61
  mountedRef.current = false;
40
62
  };
41
63
  },
42
- [],
64
+ [load],
43
65
  );
44
66
 
45
67
  if (value === undefined) {
@@ -48,3 +70,15 @@ export function useLoader<R>({initial, ...options}: CacheOptions<R>): R {
48
70
 
49
71
  return value;
50
72
  }
73
+
74
+ type Callback = () => void;
75
+
76
+ function useStableCallback(callback: Callback): Callback {
77
+ const ref = useRef<Callback>();
78
+
79
+ useEffect(() => {
80
+ ref.current = callback;
81
+ });
82
+
83
+ return useCallback(() => { ref.current?.(); }, []);
84
+ }