@livestore/livestore 0.0.46-dev.4 → 0.0.46

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.
Files changed (115) hide show
  1. package/README.md +10 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +18 -2
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +27 -3
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/__tests__/react/utils/otel.d.ts +10 -0
  8. package/dist/__tests__/react/utils/otel.d.ts.map +1 -0
  9. package/dist/__tests__/react/utils/otel.js +42 -0
  10. package/dist/__tests__/react/utils/otel.js.map +1 -0
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/react/LiveStoreProvider.js +39 -6
  15. package/dist/react/LiveStoreProvider.js.map +1 -1
  16. package/dist/react/LiveStoreProvider.test.d.ts +2 -0
  17. package/dist/react/LiveStoreProvider.test.d.ts.map +1 -0
  18. package/dist/react/LiveStoreProvider.test.js +51 -0
  19. package/dist/react/LiveStoreProvider.test.js.map +1 -0
  20. package/dist/react/components/DiffableList copy.d.ts +19 -0
  21. package/dist/react/components/DiffableList copy.d.ts.map +1 -0
  22. package/dist/react/components/DiffableList copy.js +62 -0
  23. package/dist/react/components/DiffableList copy.js.map +1 -0
  24. package/dist/react/components/DiffableList.d.ts +2 -9
  25. package/dist/react/components/DiffableList.d.ts.map +1 -1
  26. package/dist/react/components/DiffableList.js +10 -102
  27. package/dist/react/components/DiffableList.js.map +1 -1
  28. package/dist/react/components/DiffableList2.d.ts +20 -0
  29. package/dist/react/components/DiffableList2.d.ts.map +1 -0
  30. package/dist/react/components/DiffableList2.js +119 -0
  31. package/dist/react/components/DiffableList2.js.map +1 -0
  32. package/dist/react/components/DiffableList3.d.ts +19 -0
  33. package/dist/react/components/DiffableList3.d.ts.map +1 -0
  34. package/dist/react/components/DiffableList3.js +62 -0
  35. package/dist/react/components/DiffableList3.js.map +1 -0
  36. package/dist/react/components/LiveList.d.ts +21 -0
  37. package/dist/react/components/LiveList.d.ts.map +1 -0
  38. package/dist/react/components/LiveList.js +31 -0
  39. package/dist/react/components/LiveList.js.map +1 -0
  40. package/dist/react/index.d.ts +1 -1
  41. package/dist/react/index.d.ts.map +1 -1
  42. package/dist/react/index.js +1 -1
  43. package/dist/react/index.js.map +1 -1
  44. package/dist/react/useAtom.d.ts +1 -1
  45. package/dist/react/useAtom.d.ts.map +1 -1
  46. package/dist/react/useAtom.js.map +1 -1
  47. package/dist/react/useQuery.d.ts +4 -1
  48. package/dist/react/useQuery.d.ts.map +1 -1
  49. package/dist/react/useQuery.js +24 -19
  50. package/dist/react/useQuery.js.map +1 -1
  51. package/dist/react/useQuery.test.js +11 -11
  52. package/dist/react/useQuery.test.js.map +1 -1
  53. package/dist/react/useRow.d.ts.map +1 -1
  54. package/dist/react/useRow.js +14 -69
  55. package/dist/react/useRow.js.map +1 -1
  56. package/dist/react/useRow.test.js +440 -28
  57. package/dist/react/useRow.test.js.map +1 -1
  58. package/dist/react/useRowOld.d.ts +40 -0
  59. package/dist/react/useRowOld.d.ts.map +1 -0
  60. package/dist/react/useRowOld.js +134 -0
  61. package/dist/react/useRowOld.js.map +1 -0
  62. package/dist/react/useTemporaryQuery.d.ts +15 -3
  63. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  64. package/dist/react/useTemporaryQuery.js +60 -27
  65. package/dist/react/useTemporaryQuery.js.map +1 -1
  66. package/dist/react/useTemporaryQuery.test.js +10 -9
  67. package/dist/react/useTemporaryQuery.test.js.map +1 -1
  68. package/dist/reactive.d.ts +23 -5
  69. package/dist/reactive.d.ts.map +1 -1
  70. package/dist/reactive.js +44 -11
  71. package/dist/reactive.js.map +1 -1
  72. package/dist/reactive.test.js +1 -1
  73. package/dist/reactive.test.js.map +1 -1
  74. package/dist/reactiveQueries/base-class.d.ts +1 -1
  75. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  76. package/dist/reactiveQueries/base-class.js.map +1 -1
  77. package/dist/reactiveQueries/graphql.d.ts +2 -2
  78. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  79. package/dist/reactiveQueries/graphql.js +16 -10
  80. package/dist/reactiveQueries/graphql.js.map +1 -1
  81. package/dist/reactiveQueries/sql.d.ts +1 -1
  82. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  83. package/dist/reactiveQueries/sql.js +15 -11
  84. package/dist/reactiveQueries/sql.js.map +1 -1
  85. package/dist/reactiveQueries/sql.test.js +1 -40
  86. package/dist/reactiveQueries/sql.test.js.map +1 -1
  87. package/dist/store.d.ts +2 -2
  88. package/dist/store.d.ts.map +1 -1
  89. package/dist/store.js +10 -7
  90. package/dist/store.js.map +1 -1
  91. package/package.json +6 -8
  92. package/src/__tests__/react/fixture.tsx +35 -2
  93. package/src/__tests__/react/utils/otel.ts +61 -0
  94. package/src/index.ts +12 -1
  95. package/src/react/LiveStoreProvider.test.tsx +82 -0
  96. package/src/react/LiveStoreProvider.tsx +42 -7
  97. package/src/react/components/LiveList.tsx +84 -0
  98. package/src/react/index.ts +1 -1
  99. package/src/react/useAtom.ts +1 -1
  100. package/src/react/useQuery.test.tsx +11 -11
  101. package/src/react/useQuery.ts +29 -22
  102. package/src/react/useRow.test.tsx +502 -30
  103. package/src/react/useRow.ts +19 -107
  104. package/src/react/useTemporaryQuery.test.tsx +17 -16
  105. package/src/react/useTemporaryQuery.ts +96 -28
  106. package/src/reactive.test.ts +1 -1
  107. package/src/reactive.ts +76 -15
  108. package/src/reactiveQueries/base-class.ts +2 -1
  109. package/src/reactiveQueries/graphql.ts +21 -15
  110. package/src/reactiveQueries/sql.test.ts +1 -54
  111. package/src/reactiveQueries/sql.ts +20 -14
  112. package/src/store.ts +12 -8
  113. package/tsconfig.json +0 -1
  114. package/src/react/components/DiffableList.tsx +0 -192
  115. package/src/react/utils/useCleanup.ts +0 -25
@@ -1,9 +1,9 @@
1
1
  import * as otel from '@opentelemetry/api'
2
- import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
3
2
  import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
4
3
  import { describe, expect, it } from 'vitest'
5
4
 
6
5
  import { makeTodoMvc, todos } from '../__tests__/react/fixture.js'
6
+ import { getSimplifiedRootSpan } from '../__tests__/react/utils/otel.js'
7
7
  import { computed, ParseUtils, querySQL, rawSqlMutation, sql } from '../index.js'
8
8
 
9
9
  /*
@@ -299,56 +299,3 @@ describe('otel', () => {
299
299
  `)
300
300
  })
301
301
  })
302
-
303
- const compareHrTime = (a: [number, number], b: [number, number]) => {
304
- if (a[0] !== b[0]) return a[0] - b[0]
305
- return a[1] - b[1]
306
- }
307
-
308
- const omitEmpty = (obj: any) => {
309
- const result: any = {}
310
- for (const key in obj) {
311
- if (
312
- obj[key] !== undefined &&
313
- !(Array.isArray(obj[key]) && obj[key].length === 0) &&
314
- Object.keys(obj[key]).length > 0
315
- ) {
316
- result[key] = obj[key]
317
- }
318
- }
319
- return result
320
- }
321
-
322
- const getSimplifiedRootSpan = (exporter: InMemorySpanExporter) => {
323
- const spans = exporter.getFinishedSpans()
324
- const spansMap = new Map<string, NestedSpan>(spans.map((span) => [span.spanContext().spanId, { span, children: [] }]))
325
-
326
- spansMap.forEach((nestedSpan) => {
327
- const parentSpan = nestedSpan.span.parentSpanId ? spansMap.get(nestedSpan.span.parentSpanId) : undefined
328
- if (parentSpan) {
329
- parentSpan.children.push(nestedSpan)
330
- }
331
- })
332
-
333
- type NestedSpan = { span: ReadableSpan; children: NestedSpan[] }
334
- const rootSpan = spansMap.get(spans.find((_) => _.name === 'test')!.spanContext().spanId)!
335
-
336
- type SimplifiedNestedSpan = { _name: string; attributes: any; children: SimplifiedNestedSpan[] }
337
-
338
- const simplifySpan = (span: NestedSpan): SimplifiedNestedSpan =>
339
- omitEmpty({
340
- _name: span.span.name,
341
- attributes: span.span.attributes,
342
- children: span.children
343
- .filter((_) => _.span.name !== 'createStore')
344
- .sort((a, b) => compareHrTime(a.span.startTime, b.span.startTime))
345
- .map(simplifySpan),
346
- })
347
-
348
- // console.dir(
349
- // spans.map((_) => [_.spanContext().spanId, _.name, _.attributes, _.parentSpanId]),
350
- // { depth: 10 },
351
- // )
352
-
353
- return simplifySpan(rootSpan)
354
- }
@@ -49,7 +49,7 @@ export class LiveStoreSQLQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo
49
49
  _tag: 'sql' = 'sql'
50
50
 
51
51
  /** A reactive thunk representing the query text */
52
- queryString$: Thunk<string, DbContext, RefreshReason>
52
+ queryString$: Thunk<string, DbContext, RefreshReason> | undefined
53
53
 
54
54
  /** A reactive thunk representing the query results */
55
55
  results$: Thunk<TResult, DbContext, RefreshReason>
@@ -126,23 +126,23 @@ Result:`,
126
126
  ? map
127
127
  : shouldNeverHappen(`Invalid map function ${map}`)
128
128
 
129
- // TODO don't even create a thunk if query string is static
130
- const queryString$ = this.dbGraph.makeThunk(
131
- (get, setDebugInfo, { rootOtelContext }, otelContext) => {
132
- if (typeof genQueryString === 'function') {
129
+ let queryString$OrQueryString: string | Thunk<string, DbContext, RefreshReason>
130
+ if (typeof genQueryString === 'function') {
131
+ queryString$OrQueryString = this.dbGraph.makeThunk(
132
+ (get, setDebugInfo, { rootOtelContext }, otelContext) => {
133
133
  const startMs = performance.now()
134
134
  const queryString = genQueryString(makeGetAtomResult(get, otelContext ?? rootOtelContext))
135
135
  const durationMs = performance.now() - startMs
136
136
  setDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString, durationMs })
137
137
  return queryString
138
- } else {
139
- return genQueryString
140
- }
141
- },
142
- { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
143
- )
138
+ },
139
+ { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
140
+ )
144
141
 
145
- this.queryString$ = queryString$
142
+ this.queryString$ = queryString$OrQueryString
143
+ } else {
144
+ queryString$OrQueryString = genQueryString
145
+ }
146
146
 
147
147
  const queryLabel = `${label}:results`
148
148
 
@@ -162,7 +162,10 @@ Result:`,
162
162
  this.execBeforeFirstRun = undefined
163
163
  }
164
164
 
165
- const sqlString = get(queryString$, otelContext)
165
+ const sqlString =
166
+ typeof queryString$OrQueryString === 'string'
167
+ ? queryString$OrQueryString
168
+ : get(queryString$OrQueryString, otelContext)
166
169
 
167
170
  if (queriedTablesRef.current === undefined) {
168
171
  queriedTablesRef.current = store.mainDbWrapper.getTablesUsed(sqlString)
@@ -238,7 +241,10 @@ Result:`,
238
241
  // })
239
242
 
240
243
  destroy = () => {
241
- this.dbGraph.destroyNode(this.queryString$)
244
+ if (this.queryString$ !== undefined) {
245
+ this.dbGraph.destroyNode(this.queryString$)
246
+ }
247
+
242
248
  this.dbGraph.destroyNode(this.results$)
243
249
  }
244
250
  }
package/src/store.ts CHANGED
@@ -184,22 +184,23 @@ export class Store<
184
184
  * Returns a function to cancel the subscription.
185
185
  */
186
186
  subscribe = <TResult>(
187
- query: LiveQuery<TResult, any>,
187
+ query$: LiveQuery<TResult, any>,
188
188
  onNewValue: (value: TResult) => void,
189
189
  onUnsubsubscribe?: () => void,
190
190
  options?: { label?: string; otelContext?: otel.Context; skipInitialRun?: boolean } | undefined,
191
191
  ): (() => void) =>
192
192
  this.otel.tracer.startActiveSpan(
193
193
  `LiveStore.subscribe`,
194
- { attributes: { label: options?.label, queryLabel: query.label } },
194
+ { attributes: { label: options?.label, queryLabel: query$.label } },
195
195
  options?.otelContext ?? this.otel.queriesSpanContext,
196
196
  (span) => {
197
+ // console.log('store sub', query$.label)
197
198
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
198
199
 
199
200
  const label = `subscribe:${options?.label}`
200
- const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), { label })
201
+ const effect = this.graph.makeEffect((get) => onNewValue(get(query$.results$)), { label })
201
202
 
202
- this.activeQueries.add(query as LiveQuery<TResult>)
203
+ this.activeQueries.add(query$ as LiveQuery<TResult>)
203
204
 
204
205
  // Running effect right away to get initial value (unless `skipInitialRun` is set)
205
206
  if (options?.skipInitialRun !== true) {
@@ -207,9 +208,10 @@ export class Store<
207
208
  }
208
209
 
209
210
  const unsubscribe = () => {
211
+ // console.log('store unsub', query$.label)
210
212
  try {
211
213
  this.graph.destroyNode(effect)
212
- this.activeQueries.remove(query as LiveQuery<TResult>)
214
+ this.activeQueries.remove(query$ as LiveQuery<TResult>)
213
215
  onUnsubsubscribe?.()
214
216
  } finally {
215
217
  span.end()
@@ -225,7 +227,7 @@ export class Store<
225
227
  *
226
228
  * Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
227
229
  */
228
- destroy = () => {
230
+ destroy = async () => {
229
231
  for (const tableRef of Object.values(this.tableRefs)) {
230
232
  for (const superComp of tableRef.super) {
231
233
  this.graph.removeEdge(superComp, tableRef)
@@ -234,6 +236,8 @@ export class Store<
234
236
 
235
237
  otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
236
238
  otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
239
+
240
+ await this.db.storageDb.shutdown()
237
241
  }
238
242
 
239
243
  mutate: {
@@ -540,11 +544,11 @@ export const createStore = async <
540
544
 
541
545
  db.mainDb.execute('COMMIT', undefined)
542
546
 
543
- db.storageDb.execute('BEGIN', undefined, undefined)
547
+ // db.storageDb.execute('BEGIN', undefined, undefined)
544
548
  for (const [queryStr, bindValues] of txnExecuteStmnts) {
545
549
  db.storageDb.execute(queryStr, bindValues, undefined)
546
550
  }
547
- db.storageDb.execute('COMMIT', undefined, undefined)
551
+ // db.storageDb.execute('COMMIT', undefined, undefined)
548
552
  } catch (e: any) {
549
553
  db.mainDb.execute('ROLLBACK', undefined)
550
554
  throw e
package/tsconfig.json CHANGED
@@ -12,7 +12,6 @@
12
12
  "references": [
13
13
  { "path": "../../effect-db-schema" },
14
14
  { "path": "../common" },
15
- { "path": "../fractional-index" },
16
15
  { "path": "../web" },
17
16
  { "path": "../utils" }
18
17
  ]
@@ -1,192 +0,0 @@
1
- import { FI } from '@livestore/fractional-index'
2
- import { casesHandled } from '@livestore/utils'
3
- import * as itsFine from 'its-fine'
4
- import React from 'react'
5
- import ReactDOM from 'react-dom/client'
6
-
7
- import type { LiveQuery } from '../../reactiveQueries/base-class.js'
8
- import { computed } from '../../reactiveQueries/js.js'
9
- import { useQuery } from '../useQuery.js'
10
-
11
- export type Props<TItem> = {
12
- items$: LiveQuery<ReadonlyArray<TItem>>
13
- /**
14
- * @example
15
- * ```tsx
16
- * renderContainer={(children) => <ul>{children}</ul>}
17
- * ```
18
- */
19
- renderContainer: (ref: React.LegacyRef<any>) => React.ReactNode
20
- // TODO refactor render-flag to allow for transition animations on add/remove
21
- renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
22
- getKey: (item: TItem, index: number) => string | number
23
- }
24
-
25
- export const DiffableList_ = <TItem,>({
26
- items$,
27
- renderContainer,
28
- renderItem,
29
- getKey,
30
- }: Props<TItem>): React.ReactNode => {
31
- const ref = React.useRef<HTMLElement>(null)
32
- const container = renderContainer(ref)
33
-
34
- const [hasMounted, setHasMounted] = React.useState(false)
35
-
36
- React.useEffect(() => setHasMounted(true), [])
37
-
38
- const keys$ = computed((get) => get(items$).map(getKey))
39
- type RefEl = {
40
- id: string | number
41
- el: HTMLElement
42
- item$: LiveQuery<TItem>
43
- root: ReactDOM.Root
44
- }
45
- const elsRef = React.useRef<RefEl[]>([])
46
-
47
- const ContextBridge = itsFine.useContextBridge()
48
-
49
- const renderListEl = React.useCallback(
50
- (parentEl: HTMLElement, index: number, item$: LiveQuery<TItem>) => {
51
- const root = ReactDOM.createRoot(parentEl)
52
- root.render(
53
- <ContextBridge>
54
- <ItemWrapper item$={item$} renderItem={renderItem} opts={{ index, isInitialListRender: !hasMounted }} />
55
- </ContextBridge>,
56
- )
57
-
58
- return root
59
- },
60
- [ContextBridge, hasMounted, renderItem],
61
- )
62
-
63
- React.useLayoutEffect(() => {
64
- if (ref.current === null) {
65
- throw new Error('ref.current is null')
66
- }
67
-
68
- const keys = keys$.run()
69
-
70
- for (let index = 0; index < keys.length; index++) {
71
- const parentEl = document.createElement('div')
72
- ref.current!.append(parentEl)
73
- const item$ = computed((get) => get(items$)[index]!) as LiveQuery<TItem>
74
- const root = renderListEl(parentEl, index, item$)
75
- elsRef.current.push({ el: parentEl, item$, root, id: keys[index]! })
76
- }
77
- // eslint-disable-next-line react-hooks/exhaustive-deps
78
- }, [])
79
-
80
- React.useEffect(() => () => keys$.destroy(), [keys$])
81
-
82
- React.useEffect(() => {
83
- // const keys = keys$.run()
84
-
85
- return keys$.subscribe((keys) => {
86
- const prevKeys = elsRef.current.map((el) => el.id)
87
-
88
- let arrayIsEqual = true
89
- for (let i = 0; i < keys.length; i++) {
90
- if (keys[i] !== prevKeys[i]) {
91
- arrayIsEqual = false
92
- break
93
- }
94
- }
95
- if (arrayIsEqual) return
96
-
97
- const previousAgg = FI.aggregateMake(prevKeys, FI.fractionalIndexImplNumber)
98
- const { newEvents } = FI.getNewEvents(previousAgg, keys, FI.fractionalIndexImplNumber)
99
-
100
- console.log('newEvents', newEvents)
101
-
102
- for (const event of newEvents) {
103
- switch (event.op) {
104
- case 'remove': {
105
- const { index } = event
106
- const el = elsRef.current[index]!
107
- el.root.unmount()
108
- el.el.remove()
109
- el.item$.destroy()
110
- elsRef.current.splice(index, 1)
111
- break
112
- }
113
- case 'add': {
114
- const { index } = event
115
- const parentEl = document.createElement('div')
116
- ref.current!.append(parentEl)
117
- const item$ = computed((get) => get(items$)[index]!) as LiveQuery<TItem>
118
- const root = renderListEl(parentEl, index, item$)
119
- elsRef.current.splice(index, 0, { el: parentEl, item$, root, id: keys[index]! })
120
- break
121
- }
122
- case 'move': {
123
- // const { newIndex, previousIndex } = event
124
-
125
- // const el = elsRef.current[previousIndex]!
126
- // const item$ = el.item$
127
- // const root = el.root
128
- // const elEl = el.el
129
-
130
- // elsRef.current.splice(previousIndex, 1)
131
- // elsRef.current.splice(newIndex, 0, { el: elEl, item$, root })
132
-
133
- // ref.current!.insertBefore(elEl, elsRef.current[newIndex + 1]?.el)
134
-
135
- // // move dom element
136
-
137
- break
138
- }
139
- default: {
140
- casesHandled(event)
141
- }
142
- }
143
- }
144
- })
145
-
146
- // for (let index = 0; index < keys.length; index++) {
147
- // if (prevKeys[index] === keys[index]) continue
148
-
149
- // // check if `keys[index]` === `prevKeys[index + 1]`
150
- // // which probably means that
151
- // if (keys[index] === prevKeys[index + 1]) {
152
- // // sp
153
- // }
154
-
155
- // prevKeys[index] = keys[index] as any
156
- // }
157
-
158
- // TODO in the future use a more efficient diffing algorithm that re-uses elements more optimally
159
- // right now we're only looking one step ahead
160
-
161
- // reconcile until `keys` and `prevKeys` are equal
162
-
163
- // prevKeys = keys
164
- }, [items$, keys$, renderListEl])
165
-
166
- return <>{container}</>
167
- }
168
-
169
- export const DiffableList = <TItem,>({
170
- items$,
171
- renderContainer,
172
- renderItem,
173
- getKey,
174
- }: Props<TItem>): React.ReactNode => (
175
- <itsFine.FiberProvider>
176
- <DiffableList_ items$={items$} renderContainer={renderContainer} renderItem={renderItem} getKey={getKey} />
177
- </itsFine.FiberProvider>
178
- )
179
-
180
- const ItemWrapper = <TItem,>({
181
- item$,
182
- opts,
183
- renderItem,
184
- }: {
185
- item$: LiveQuery<TItem>
186
- opts: { index: number; isInitialListRender: boolean }
187
- renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
188
- }) => {
189
- const item = useQuery(item$)
190
-
191
- return <>{renderItem(item, opts)}</>
192
- }
@@ -1,25 +0,0 @@
1
- import React from 'react'
2
-
3
- /**
4
- * Like cleanup callback of `React.useEffect` but running as part of the render loop.
5
- *
6
- * NOTE: This hook should not be used with React strict mode.
7
- */
8
- export const useCleanup = (
9
- /** Needs to be a `React.useCallback` value */
10
- cleanupCallback: () => void,
11
- ) => {
12
- const callbackRef = React.useRef(cleanupCallback)
13
-
14
- if (callbackRef.current !== cleanupCallback) {
15
- callbackRef.current()
16
- callbackRef.current = cleanupCallback
17
- }
18
-
19
- React.useEffect(
20
- () => () => {
21
- callbackRef.current()
22
- },
23
- [],
24
- )
25
- }