Skip to content

Commit e1fcc3d

Browse files
committed
perf: skip unnecessary rerender
1 parent 23d6aba commit e1fcc3d

File tree

2 files changed

+62
-13
lines changed

2 files changed

+62
-13
lines changed

src/methods/get.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function get<
7272
if (missing.length) {
7373
debugLog('Get from idb: %s', missing.join(', '))
7474

75+
let hit = false
7576
const idbPromise = getMany(missing, store).then(
7677
obj => {
7778
debugLog.enabled && debugLog(
@@ -82,6 +83,7 @@ export function get<
8283
const stillMissing: string[] = []
8384
obj.forEach((obj, i) => {
8485
if (verifyEntry({ obj }, expire)) {
86+
hit = true
8587
cache[ missing[i] ] = { obj }
8688
} else {
8789
stillMissing.push(missing[i])
@@ -104,7 +106,7 @@ export function get<
104106
},
105107
)
106108

107-
idbPromise.then(rerender)
109+
idbPromise.then(() => hit && rerender())
108110

109111
missing.forEach((key, i) => {
110112
cache[key] = cache[key] ?? {}

test/useCached.tsx

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,49 @@
1-
import React from 'react'
2-
import { act, render } from '@testing-library/react'
1+
import React, { useContext } from 'react'
2+
import { act, render, waitFor } from '@testing-library/react'
33
import { useCached } from '../src'
4-
import { clear } from 'idb-keyval'
4+
import { clear, createStore } from 'idb-keyval'
5+
import { CacheContext } from '../src/context'
56

6-
function setup(...propsArray: Parameters<typeof useCached>[0][]) {
7+
async function setup(
8+
propsArray: Parameters<typeof useCached>[0][] = [undefined],
9+
renderArray: ((api: ReturnType<typeof useCached>) => void)[] = [],
10+
) {
711
const hook: ReturnType<typeof useCached>[] = []
8-
function TestComponent({i = 0, p = {}}: {i?: number, p?: Parameters<typeof useCached>[0]} = {}) {
12+
function TestComponent({
13+
i = 0,
14+
p = {},
15+
r = () => { return },
16+
}: {
17+
i?: number,
18+
p?: Parameters<typeof useCached>[0],
19+
r?: (api: ReturnType<typeof useCached>) => void,
20+
} = {}) {
921
hook[i] = useCached(Object.keys(p).length ? p : undefined)
22+
r(hook[i])
1023
return null
1124
}
1225

26+
// clear IndexedDb and default reactCache to get a fresh start
27+
for (const p of propsArray) {
28+
const store = createStore(p?.dbName ?? 'Cached', p?.storeName ?? 'keyval')
29+
await clear(store)
30+
}
31+
function ClearComponent() {
32+
const cache = useContext(CacheContext)
33+
Object.keys(cache).forEach(k => { delete cache[k] })
34+
return null
35+
}
36+
render(<ClearComponent/>)
37+
1338
render(<>
14-
{(propsArray.length ? propsArray : [undefined]).map((p, i) => <TestComponent key={i} i={i} p={p} />)}
39+
{propsArray.map((p, i) => <TestComponent key={i} i={i} p={p} r={renderArray[i]}/>)}
1540
</>)
1641

1742
return { getHook: (i = 0) => hook[i] }
1843
}
1944

20-
it('provide api', () => {
21-
const { getHook } = setup()
45+
it('provide api', async () => {
46+
const { getHook } = await setup()
2247

2348
expect(getHook()).toEqual(expect.objectContaining({
2449
clear: expect.any(Function),
@@ -28,8 +53,8 @@ it('provide api', () => {
2853
}))
2954
})
3055

31-
it('provide api with custom store', () => {
32-
const { getHook } = setup({dbName: 'foo', storeName: 'bar'})
56+
it('provide api with custom store', async () => {
57+
const { getHook } = await setup([{dbName: 'foo', storeName: 'bar'}])
3358

3459
expect(getHook()).toEqual(expect.objectContaining({
3560
clear: expect.any(Function),
@@ -40,7 +65,7 @@ it('provide api with custom store', () => {
4065
})
4166

4267
it('get and set without context', async () => {
43-
const { getHook } = setup({context: false}, {context: false})
68+
const { getHook } = await setup([{context: false}, {context: false}])
4469

4570
await act(async () => {
4671
getHook(0).set('foo', 'bar')
@@ -52,7 +77,7 @@ it('get and set without context', async () => {
5277
})
5378

5479
it('get and set with context', async () => {
55-
const { getHook } = setup({context: true}, {context: true})
80+
const { getHook } = await setup([{context: true}, {context: true}])
5681

5782
await act(async () => {
5883
getHook(0).set('foo', 'bar')
@@ -62,3 +87,25 @@ it('get and set with context', async () => {
6287
expect(getHook(0).get('foo')).toBe('bar')
6388
expect(getHook(1).get('foo')).toBe('bar')
6489
})
90+
91+
it('rerender component on updated cache', async () => {
92+
let loadingResolve: (() => void) | undefined = undefined
93+
const render = jest.fn((api: ReturnType<typeof useCached>) => {
94+
return api.get('foo', async () => new Promise(res => {
95+
api.set('foo', 'bar')
96+
loadingResolve = res
97+
}))
98+
})
99+
await setup([undefined], [render])
100+
101+
expect(render).toReturnTimes(1)
102+
expect(render).toHaveNthReturnedWith(1, undefined)
103+
104+
await waitFor(() => expect(loadingResolve).toBeTruthy())
105+
act(() => {
106+
loadingResolve && loadingResolve()
107+
})
108+
109+
expect(render).toReturnTimes(2)
110+
expect(render).toHaveNthReturnedWith(2, 'bar')
111+
})

0 commit comments

Comments
 (0)