muya 2.4.3 → 2.4.5
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.
- package/cjs/index.js +1 -1
- package/esm/scheduler.js +1 -1
- package/esm/sqlite/create-sqlite.js +1 -1
- package/esm/sqlite/index.js +1 -1
- package/esm/sqlite/table/table.js +2 -2
- package/esm/sqlite/use-sqlite-count.js +1 -0
- package/esm/sqlite/use-sqlite.js +1 -1
- package/package.json +1 -1
- package/src/scheduler.ts +8 -2
- package/src/sqlite/__tests__/use-slite-count.test.tsx +96 -0
- package/src/sqlite/__tests__/use-sqlite.test.tsx +124 -41
- package/src/sqlite/create-sqlite.ts +44 -235
- package/src/sqlite/index.ts +1 -1
- package/src/sqlite/table/table.ts +3 -3
- package/src/sqlite/table/table.types.ts +9 -1
- package/src/sqlite/use-sqlite-count.ts +69 -0
- package/src/sqlite/use-sqlite.ts +157 -89
- package/types/scheduler.d.ts +1 -1
- package/types/sqlite/create-sqlite.d.ts +6 -11
- package/types/sqlite/index.d.ts +1 -1
- package/types/sqlite/table/table.d.ts +1 -1
- package/types/sqlite/table/table.types.d.ts +8 -1
- package/types/sqlite/use-sqlite-count.d.ts +17 -0
- package/types/sqlite/use-sqlite.d.ts +18 -10
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
/* eslint-disable sonarjs/redundant-type-aliases */
|
|
2
1
|
import { STATE_SCHEDULER } from '../create'
|
|
3
2
|
import { getId } from '../utils/id'
|
|
4
|
-
import { shallow } from '../utils/shallow'
|
|
5
|
-
import { selectSql, type CreateState } from './select-sql'
|
|
6
3
|
import type { Backend } from './table'
|
|
7
|
-
import { createTable
|
|
4
|
+
import { createTable } from './table/table'
|
|
8
5
|
import type { DbOptions, DocType, Key, MutationResult, SearchOptions, Table } from './table/table.types'
|
|
9
6
|
import type { Where } from './table/where'
|
|
10
7
|
|
|
11
|
-
type SearchId = string
|
|
12
|
-
|
|
13
8
|
export interface CreateSqliteOptions<Document extends DocType> extends Omit<DbOptions<Document>, 'backend'> {
|
|
14
9
|
readonly backend: Backend | Promise<Backend>
|
|
15
10
|
}
|
|
16
11
|
|
|
17
|
-
export interface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
readonly getSnapshot: (searchId: SearchId) => Document[]
|
|
22
|
-
readonly refresh: (searchId: SearchId) => Promise<void>
|
|
12
|
+
export interface MutationItems {
|
|
13
|
+
mutations?: MutationResult[]
|
|
14
|
+
removedAll?: boolean
|
|
15
|
+
}
|
|
23
16
|
|
|
17
|
+
export interface SyncTable<Document extends DocType> {
|
|
18
|
+
readonly subscribe: (listener: (mutation: MutationItems) => void) => () => void
|
|
24
19
|
readonly set: (document: Document) => Promise<MutationResult>
|
|
25
20
|
readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>
|
|
26
21
|
readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>
|
|
@@ -29,19 +24,7 @@ export interface SyncTable<Document extends DocType> {
|
|
|
29
24
|
readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>
|
|
30
25
|
readonly count: (options?: { where?: Where<Document> }) => Promise<number>
|
|
31
26
|
readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>
|
|
32
|
-
readonly
|
|
33
|
-
readonly next: (searchId: SearchId) => Promise<boolean>
|
|
34
|
-
readonly clear: (searchId: SearchId) => void
|
|
35
|
-
|
|
36
|
-
readonly select: <Params extends unknown[]>(
|
|
37
|
-
compute: (...args: Params) => SearchOptions<Document>,
|
|
38
|
-
) => CreateState<Document, Params>
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface DataItems<Document extends DocType> {
|
|
42
|
-
items: Document[]
|
|
43
|
-
keys: Set<Key>
|
|
44
|
-
options?: SearchOptions<Document, unknown>
|
|
27
|
+
readonly clear: () => void
|
|
45
28
|
}
|
|
46
29
|
|
|
47
30
|
/**
|
|
@@ -50,17 +33,6 @@ interface DataItems<Document extends DocType> {
|
|
|
50
33
|
* @returns A SyncTable instance with methods to interact with the underlying Table and manage reactive searches
|
|
51
34
|
*/
|
|
52
35
|
export function createSqliteState<Document extends DocType>(options: CreateSqliteOptions<Document>): SyncTable<Document> {
|
|
53
|
-
const id = getId()
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Get a unique schedule ID for a search ID
|
|
57
|
-
* @param searchId The search ID
|
|
58
|
-
* @returns The unique schedule ID
|
|
59
|
-
*/
|
|
60
|
-
function getScheduleId(searchId: SearchId) {
|
|
61
|
-
return `state-${id}-search-${searchId}`
|
|
62
|
-
}
|
|
63
|
-
|
|
64
36
|
let cachedTable: Table<Document> | undefined
|
|
65
37
|
/**
|
|
66
38
|
* Get or create the underlying table
|
|
@@ -75,182 +47,74 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
75
47
|
return cachedTable
|
|
76
48
|
}
|
|
77
49
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const cachedData = new Map<SearchId, DataItems<Document>>()
|
|
84
|
-
const listeners = new Map<SearchId, Map<string, () => void>>()
|
|
85
|
-
const iterators = new Map<SearchId, AsyncIterableIterator<NextResult>>()
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Next step in the iterator
|
|
89
|
-
* @param searchId The search ID
|
|
90
|
-
* @param data The data items to process
|
|
91
|
-
* @returns boolean indicating if new items were added
|
|
92
|
-
*/
|
|
93
|
-
async function next(searchId: SearchId, data: DataItems<Document>): Promise<boolean> {
|
|
94
|
-
const iterator = iterators.get(searchId)
|
|
95
|
-
const { options: nextOptions = {} } = data
|
|
96
|
-
const { stepSize = DEFAULT_STEP_SIZE } = nextOptions
|
|
97
|
-
if (!iterator) return false
|
|
98
|
-
const newItems: Document[] = []
|
|
99
|
-
|
|
100
|
-
for (let index = 0; index < stepSize; index++) {
|
|
101
|
-
const result = await iterator.next()
|
|
102
|
-
if (result.done) {
|
|
103
|
-
iterators.delete(searchId)
|
|
104
|
-
break
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!data.keys.has(String(result.value.key))) {
|
|
108
|
-
newItems.push(result.value.document)
|
|
109
|
-
data.keys.add(String(result.value.key))
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (newItems.length === 0) return false
|
|
114
|
-
if (shallow(data.items, newItems)) return false
|
|
115
|
-
data.items = [...data.items, ...newItems]
|
|
116
|
-
return true
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Notify listeners of up dates
|
|
121
|
-
* @param searchId The search ID to notify
|
|
122
|
-
*/
|
|
123
|
-
function notifyListeners(searchId: SearchId) {
|
|
124
|
-
const searchListeners = listeners.get(searchId)
|
|
125
|
-
if (searchListeners) {
|
|
126
|
-
for (const [, listener] of searchListeners) {
|
|
127
|
-
listener()
|
|
50
|
+
const id = getId()
|
|
51
|
+
STATE_SCHEDULER.add(id, {
|
|
52
|
+
onScheduleDone(unknownItems) {
|
|
53
|
+
if (!unknownItems) {
|
|
54
|
+
return
|
|
128
55
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
* @param searchId The search ID to refresh
|
|
135
|
-
*/
|
|
136
|
-
async function refreshCache(searchId: SearchId) {
|
|
137
|
-
const table = await getTable()
|
|
138
|
-
const data = cachedData.get(searchId)
|
|
139
|
-
if (!data) return
|
|
140
|
-
const { options: refreshOptions } = data
|
|
141
|
-
const iterator = table.search({ ...refreshOptions, select: (document, { rowId, key }) => ({ document, rowId, key }) })
|
|
142
|
-
iterators.set(searchId, iterator)
|
|
143
|
-
data.keys = new Set()
|
|
144
|
-
data.items = []
|
|
145
|
-
await next(searchId, data)
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Refresh the data and notify listeners
|
|
149
|
-
* @param searchId The search ID to refresh
|
|
150
|
-
*/
|
|
151
|
-
async function refresh(searchId: SearchId) {
|
|
152
|
-
await refreshCache(searchId)
|
|
153
|
-
notifyListeners(searchId)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Handle changes to the data
|
|
158
|
-
* @param mutationResult The mutation result
|
|
159
|
-
* @returns A set of search IDs that need to be updated
|
|
160
|
-
*/
|
|
161
|
-
function handleChange(mutationResult: MutationResult) {
|
|
162
|
-
const { key, op } = mutationResult
|
|
163
|
-
// find all cached data with key
|
|
164
|
-
const searchIds = new Set<SearchId>()
|
|
165
|
-
for (const [searchId, { keys }] of cachedData) {
|
|
166
|
-
switch (op) {
|
|
167
|
-
case 'delete':
|
|
168
|
-
case 'update': {
|
|
169
|
-
if (keys.has(String(key))) {
|
|
170
|
-
searchIds.add(searchId)
|
|
171
|
-
}
|
|
172
|
-
break
|
|
56
|
+
const items = unknownItems as MutationItems[]
|
|
57
|
+
const merged: MutationItems = {}
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
if (item.removedAll) {
|
|
60
|
+
merged.removedAll = true
|
|
173
61
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
62
|
+
if (item.mutations) {
|
|
63
|
+
if (!merged.mutations) {
|
|
64
|
+
merged.mutations = []
|
|
65
|
+
}
|
|
66
|
+
merged.mutations.push(...item.mutations)
|
|
178
67
|
}
|
|
179
68
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Handle multiple changes
|
|
186
|
-
* @param mutationResults The array of mutation results
|
|
187
|
-
*/
|
|
188
|
-
async function handleChanges(mutationResults: MutationResult[]) {
|
|
189
|
-
const updateSearchIds = new Set<SearchId>()
|
|
190
|
-
for (const mutationResult of mutationResults) {
|
|
191
|
-
const searchIds = handleChange(mutationResult)
|
|
192
|
-
for (const searchId of searchIds) {
|
|
193
|
-
updateSearchIds.add(searchId)
|
|
69
|
+
for (const listener of listeners) {
|
|
70
|
+
listener(merged)
|
|
194
71
|
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// const promises = []
|
|
198
|
-
for (const searchId of updateSearchIds) {
|
|
199
|
-
const scheduleId = getScheduleId(searchId)
|
|
200
|
-
STATE_SCHEDULER.schedule(scheduleId, { searchId })
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const clearSchedulers = new Set<() => void>()
|
|
72
|
+
},
|
|
73
|
+
})
|
|
205
74
|
|
|
206
75
|
/**
|
|
207
|
-
*
|
|
208
|
-
* @param
|
|
209
|
-
* @param registerDataOptions Optional search options
|
|
210
|
-
* @returns The data items for the search ID
|
|
76
|
+
* Notify all subscribers of changes
|
|
77
|
+
* @param item The mutation items to notify subscribers about
|
|
211
78
|
*/
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
cachedData.set(searchId, { items: [], options: registerDataOptions, keys: new Set() })
|
|
215
|
-
if (registerDataOptions) {
|
|
216
|
-
refresh(searchId)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
const data = cachedData.get(searchId)!
|
|
220
|
-
if (registerDataOptions) {
|
|
221
|
-
data.options = registerDataOptions
|
|
222
|
-
}
|
|
223
|
-
return data
|
|
79
|
+
function handleChanges(item: MutationItems) {
|
|
80
|
+
STATE_SCHEDULER.schedule(id, item)
|
|
224
81
|
}
|
|
225
82
|
|
|
83
|
+
const listeners = new Set<(mutation: MutationItems) => void>()
|
|
84
|
+
|
|
226
85
|
const state: SyncTable<Document> = {
|
|
227
|
-
|
|
228
|
-
|
|
86
|
+
subscribe(listener) {
|
|
87
|
+
listeners.add(listener)
|
|
88
|
+
return () => listeners.delete(listener)
|
|
89
|
+
},
|
|
90
|
+
clear() {
|
|
91
|
+
cachedTable?.clear()
|
|
92
|
+
handleChanges({ removedAll: true })
|
|
229
93
|
},
|
|
230
94
|
async set(document) {
|
|
231
95
|
const table = await getTable()
|
|
232
96
|
const changes = await table.set(document)
|
|
233
|
-
|
|
97
|
+
handleChanges({ mutations: [changes] })
|
|
234
98
|
return changes
|
|
235
99
|
},
|
|
236
100
|
async batchSet(documents) {
|
|
237
101
|
const table = await getTable()
|
|
238
102
|
const changes = await table.batchSet(documents)
|
|
239
|
-
|
|
103
|
+
handleChanges({ mutations: changes })
|
|
240
104
|
return changes
|
|
241
105
|
},
|
|
242
106
|
async delete(key) {
|
|
243
107
|
const table = await getTable()
|
|
244
108
|
const changes = await table.delete(key)
|
|
245
109
|
if (changes) {
|
|
246
|
-
|
|
110
|
+
handleChanges({ mutations: [changes] })
|
|
247
111
|
}
|
|
248
112
|
return changes
|
|
249
113
|
},
|
|
250
114
|
async deleteBy(where) {
|
|
251
115
|
const table = await getTable()
|
|
252
116
|
const changes = await table.deleteBy(where)
|
|
253
|
-
|
|
117
|
+
handleChanges({ mutations: changes })
|
|
254
118
|
return changes
|
|
255
119
|
},
|
|
256
120
|
async get(key, selector) {
|
|
@@ -267,61 +131,6 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
267
131
|
const table = await getTable()
|
|
268
132
|
return await table.count(countOptions)
|
|
269
133
|
},
|
|
270
|
-
|
|
271
|
-
updateSearchOptions(searchId, updateSearchOptions) {
|
|
272
|
-
const data = registerData(searchId, updateSearchOptions)
|
|
273
|
-
data.options = updateSearchOptions
|
|
274
|
-
const scheduleId = getScheduleId(searchId)
|
|
275
|
-
STATE_SCHEDULER.schedule(scheduleId, { searchId })
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
subscribe(searchId, componentId, listener) {
|
|
279
|
-
const scheduleId = getScheduleId(searchId)
|
|
280
|
-
const clearScheduler = STATE_SCHEDULER.add(scheduleId, {
|
|
281
|
-
onScheduleDone() {
|
|
282
|
-
refresh(searchId)
|
|
283
|
-
},
|
|
284
|
-
})
|
|
285
|
-
clearSchedulers.add(clearScheduler)
|
|
286
|
-
|
|
287
|
-
if (!listeners.has(searchId)) {
|
|
288
|
-
listeners.set(searchId, new Map())
|
|
289
|
-
}
|
|
290
|
-
const searchListeners = listeners.get(searchId)!
|
|
291
|
-
searchListeners.set(componentId, listener)
|
|
292
|
-
return () => {
|
|
293
|
-
searchListeners.delete(componentId)
|
|
294
|
-
if (searchListeners.size === 0) {
|
|
295
|
-
listeners.delete(searchId)
|
|
296
|
-
}
|
|
297
|
-
clearScheduler()
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
getSnapshot(searchId) {
|
|
301
|
-
const data = registerData(searchId)
|
|
302
|
-
return data.items
|
|
303
|
-
},
|
|
304
|
-
refresh,
|
|
305
|
-
destroy() {
|
|
306
|
-
for (const clear of clearSchedulers) clear()
|
|
307
|
-
cachedData.clear()
|
|
308
|
-
listeners.clear()
|
|
309
|
-
},
|
|
310
|
-
async next(searchId) {
|
|
311
|
-
const data = cachedData.get(searchId)
|
|
312
|
-
if (data) {
|
|
313
|
-
const hasNext = await next(searchId, data)
|
|
314
|
-
if (hasNext) {
|
|
315
|
-
notifyListeners(searchId)
|
|
316
|
-
}
|
|
317
|
-
return hasNext
|
|
318
|
-
}
|
|
319
|
-
return false
|
|
320
|
-
},
|
|
321
|
-
|
|
322
|
-
select(compute) {
|
|
323
|
-
return selectSql(state, compute)
|
|
324
|
-
},
|
|
325
134
|
}
|
|
326
135
|
|
|
327
136
|
return state
|
package/src/sqlite/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { Where } from './where'
|
|
|
9
9
|
import { getWhereQuery } from './where'
|
|
10
10
|
|
|
11
11
|
const DELETE_IN_CHUNK = 500
|
|
12
|
-
export const
|
|
12
|
+
export const DEFAULT_PAGE_SIZE = 100
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Convert a dot-separated path to a JSON path
|
|
@@ -239,7 +239,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
|
|
|
239
239
|
offset = 0,
|
|
240
240
|
where,
|
|
241
241
|
select = (document) => document as unknown as Selected,
|
|
242
|
-
|
|
242
|
+
pageSize = DEFAULT_PAGE_SIZE,
|
|
243
243
|
} = options
|
|
244
244
|
|
|
245
245
|
const whereSql = getWhereQuery<Document>(where, tableName)
|
|
@@ -256,7 +256,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
|
|
|
256
256
|
query += hasUserKey ? ` ORDER BY key COLLATE NOCASE ${order.toUpperCase()}` : ` ORDER BY rowid ${order.toUpperCase()}`
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
const batchLimit = limit ? Math.min(
|
|
259
|
+
const batchLimit = limit ? Math.min(pageSize, limit - yielded) : pageSize
|
|
260
260
|
query += ` LIMIT ${batchLimit} OFFSET ${currentOffset}`
|
|
261
261
|
|
|
262
262
|
const results = await backend.select<Array<{ rowid: number; data: string }>>(query)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { SqlSeachOptions } from '../select-sql'
|
|
2
1
|
import type { Backend } from './backend'
|
|
3
2
|
import type { FtsTokenizerOptions } from './tokenizer'
|
|
4
3
|
import type { Where } from './where'
|
|
@@ -7,6 +6,15 @@ import type { Where } from './where'
|
|
|
7
6
|
export type DocType = { [key: string]: any }
|
|
8
7
|
export type KeyTypeAvailable = 'string' | 'number'
|
|
9
8
|
|
|
9
|
+
export interface SqlSeachOptions<Document extends DocType> {
|
|
10
|
+
readonly sortBy?: DotPath<Document>
|
|
11
|
+
readonly order?: 'asc' | 'desc'
|
|
12
|
+
readonly limit?: number
|
|
13
|
+
readonly offset?: number
|
|
14
|
+
readonly where?: Where<Document>
|
|
15
|
+
readonly pageSize?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
// Expand all nested keys into dot-paths
|
|
11
19
|
export type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`
|
|
12
20
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useReducer, useRef, type DependencyList } from 'react'
|
|
2
|
+
import type { SyncTable } from './create-sqlite'
|
|
3
|
+
import type { DocType } from './table/table.types'
|
|
4
|
+
import type { Where } from './table/where'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A React hook to count the number of items in a SyncTable reactively.
|
|
8
|
+
* It updates the count when items are inserted or deleted, but ignores updates.
|
|
9
|
+
* Supports filtering the count using a `where` clause.
|
|
10
|
+
* @param state The SyncTable instance to observe.
|
|
11
|
+
* @param options Optional filtering options.
|
|
12
|
+
* @param options.where A `where` clause to filter the count.
|
|
13
|
+
* @param deps Dependency list to control when to re-run the effect.
|
|
14
|
+
* @returns The current count of items in the table.
|
|
15
|
+
*/
|
|
16
|
+
export function useSqliteCount<Document extends DocType>(
|
|
17
|
+
state: SyncTable<Document>,
|
|
18
|
+
options: { where?: Where<Document> } = {},
|
|
19
|
+
deps: DependencyList = [],
|
|
20
|
+
): number {
|
|
21
|
+
const countRef = useRef(0)
|
|
22
|
+
const [, rerender] = useReducer((c: number) => c + 1, 0)
|
|
23
|
+
|
|
24
|
+
const updateCount = useCallback(async () => {
|
|
25
|
+
const newCount = await state.count(options)
|
|
26
|
+
countRef.current = newCount
|
|
27
|
+
rerender()
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, deps)
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
updateCount()
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, deps)
|
|
35
|
+
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
const unsubscribe = state.subscribe((item) => {
|
|
38
|
+
const { mutations, removedAll } = item
|
|
39
|
+
if (removedAll) {
|
|
40
|
+
countRef.current = 0
|
|
41
|
+
rerender()
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (!mutations) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let shouldUpdate = false
|
|
49
|
+
for (const mutation of mutations) {
|
|
50
|
+
const { op } = mutation
|
|
51
|
+
if (op === 'insert' || op === 'delete') {
|
|
52
|
+
shouldUpdate = true
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (shouldUpdate) {
|
|
58
|
+
updateCount()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
unsubscribe()
|
|
64
|
+
}
|
|
65
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
|
+
}, [state])
|
|
67
|
+
|
|
68
|
+
return countRef.current
|
|
69
|
+
}
|