virtual-scroller 1.12.2 → 1.12.3
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/CHANGELOG.md +9 -0
- package/CODE_OF_CONDUCT.md +78 -0
- package/README.md +1 -1
- package/bundle/virtual-scroller-dom.js +1 -1
- package/bundle/virtual-scroller-dom.js.map +1 -1
- package/bundle/virtual-scroller-react.js +1 -1
- package/bundle/virtual-scroller-react.js.map +1 -1
- package/bundle/virtual-scroller.js +1 -1
- package/bundle/virtual-scroller.js.map +1 -1
- package/commonjs/Layout.js +1 -1
- package/commonjs/Layout.js.map +1 -1
- package/commonjs/Scroll.js +2 -1
- package/commonjs/Scroll.js.map +1 -1
- package/commonjs/VirtualScroller.js +62 -23
- package/commonjs/VirtualScroller.js.map +1 -1
- package/commonjs/VirtualScroller.layout.js +16 -10
- package/commonjs/VirtualScroller.layout.js.map +1 -1
- package/commonjs/VirtualScroller.onRender.js +2 -2
- package/commonjs/VirtualScroller.onRender.js.map +1 -1
- package/commonjs/react/VirtualScroller.js +3 -0
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/react/useEffectDontMountTwiceInStrictMode.js +83 -0
- package/commonjs/react/useEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/commonjs/react/useInsertionEffectDontMountTwiceInStrictMode.js +20 -0
- package/commonjs/react/useInsertionEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/commonjs/react/useLayoutEffectDontMountTwiceInStrictMode.js +20 -0
- package/commonjs/react/useLayoutEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/commonjs/react/useState.js +13 -7
- package/commonjs/react/useState.js.map +1 -1
- package/commonjs/react/useStateNoStaleBug.js +59 -0
- package/commonjs/react/useStateNoStaleBug.js.map +1 -0
- package/modules/Layout.js +1 -1
- package/modules/Layout.js.map +1 -1
- package/modules/Scroll.js +2 -1
- package/modules/Scroll.js.map +1 -1
- package/modules/VirtualScroller.js +62 -23
- package/modules/VirtualScroller.js.map +1 -1
- package/modules/VirtualScroller.layout.js +16 -10
- package/modules/VirtualScroller.layout.js.map +1 -1
- package/modules/VirtualScroller.onRender.js +2 -2
- package/modules/VirtualScroller.onRender.js.map +1 -1
- package/modules/react/VirtualScroller.js +3 -1
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/react/useEffectDontMountTwiceInStrictMode.js +75 -0
- package/modules/react/useEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/modules/react/useInsertionEffectDontMountTwiceInStrictMode.js +9 -0
- package/modules/react/useInsertionEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/modules/react/useLayoutEffectDontMountTwiceInStrictMode.js +9 -0
- package/modules/react/useLayoutEffectDontMountTwiceInStrictMode.js.map +1 -0
- package/modules/react/useState.js +11 -8
- package/modules/react/useState.js.map +1 -1
- package/modules/react/useStateNoStaleBug.js +51 -0
- package/modules/react/useStateNoStaleBug.js.map +1 -0
- package/package.json +1 -1
- package/source/Layout.js +1 -1
- package/source/Scroll.js +1 -0
- package/source/VirtualScroller.js +61 -21
- package/source/VirtualScroller.layout.js +14 -8
- package/source/VirtualScroller.onRender.js +2 -2
- package/source/react/VirtualScroller.js +3 -0
- package/source/react/useEffectDontMountTwiceInStrictMode.js +68 -0
- package/source/react/useInsertionEffectDontMountTwiceInStrictMode.js +10 -0
- package/source/react/useLayoutEffectDontMountTwiceInStrictMode.js +10 -0
- package/source/react/useState.js +8 -5
- package/source/react/useStateNoStaleBug.js +35 -0
|
@@ -49,7 +49,11 @@ export default class VirtualScroller {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
if (isRestart) {
|
|
53
|
+
log('~ Start (restart) ~')
|
|
54
|
+
} else {
|
|
55
|
+
log('~ Start ~')
|
|
56
|
+
}
|
|
53
57
|
|
|
54
58
|
// `this._isActive = true` should be placed somewhere at the start of this function.
|
|
55
59
|
this._isActive = true
|
|
@@ -71,18 +75,13 @@ export default class VirtualScroller {
|
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
// If there was a pending state update that didn't get applied
|
|
75
|
-
// because
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// * `beforeResize`
|
|
82
|
-
// All of those get rewritten in `onResize()` anyway.
|
|
83
|
-
//
|
|
84
|
-
let stateUpdate = this._stoppedStateUpdate
|
|
85
|
-
this._stoppedStateUpdate = undefined
|
|
78
|
+
// If there was a pending "after render" state update that didn't get applied
|
|
79
|
+
// because the `VirtualScroller` got stopped, then apply that pending "after render"
|
|
80
|
+
// state update now. Such state update could include properties like:
|
|
81
|
+
// * A `verticalSpacing` that has been measured in `onRender()`.
|
|
82
|
+
// * A cleaned-up `beforeResize` object that was cleaned-up in `onRender()`.
|
|
83
|
+
let stateUpdate = this._afterRenderStateUpdateThatWasStopped
|
|
84
|
+
this._afterRenderStateUpdateThatWasStopped = undefined
|
|
86
85
|
|
|
87
86
|
// Reset `this.verticalSpacing` so that it re-measures it in cases when
|
|
88
87
|
// the `VirtualScroller` was previously stopped and is now being restarted.
|
|
@@ -122,12 +121,10 @@ export default class VirtualScroller {
|
|
|
122
121
|
const prevWidth = this.getState().scrollableContainerWidth
|
|
123
122
|
if (newWidth !== prevWidth) {
|
|
124
123
|
log('~ Scrollable container width changed from', prevWidth, 'to', newWidth, '~')
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// * `beforeResize`
|
|
130
|
-
// All of those get rewritten in `onResize()` anyway.
|
|
124
|
+
// The pending state update (if present) won't be applied in this case.
|
|
125
|
+
// That's ok because such state update could currently only originate in
|
|
126
|
+
// `this.onResize()` function. Therefore, alling `this.onResize()` again
|
|
127
|
+
// would rewrite all those `stateUpdate` properties anyway, so they're not passed.
|
|
131
128
|
return this.onResize()
|
|
132
129
|
}
|
|
133
130
|
}
|
|
@@ -221,7 +218,9 @@ export default class VirtualScroller {
|
|
|
221
218
|
* @param {number} i — Item index
|
|
222
219
|
*/
|
|
223
220
|
onItemHeightDidChange(i) {
|
|
224
|
-
|
|
221
|
+
// See the comments in the `setItemState()` function below for the rationale
|
|
222
|
+
// on why the `hasToBeStarted()` check was commented out.
|
|
223
|
+
// this.hasToBeStarted()
|
|
225
224
|
this._onItemHeightDidChange(i)
|
|
226
225
|
}
|
|
227
226
|
|
|
@@ -231,7 +230,48 @@ export default class VirtualScroller {
|
|
|
231
230
|
* @param {any} i — Item's new state
|
|
232
231
|
*/
|
|
233
232
|
setItemState(i, newItemState) {
|
|
234
|
-
|
|
233
|
+
// There is an issue in React 18.2.0 when `useInsertionEffect()` doesn't run twice
|
|
234
|
+
// on mount unlike `useLayoutEffect()` in "strict" mode. That causes a bug in a React
|
|
235
|
+
// implementation of the `virtual-scroller`.
|
|
236
|
+
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/33
|
|
237
|
+
// https://github.com/facebook/react/issues/26320
|
|
238
|
+
// A workaround for that bug is ignoring the second-initial run of the effects at mount.
|
|
239
|
+
//
|
|
240
|
+
// But in that case, if an `ItemComponent` calls `setItemState()` in `useLayoutEffect()`,
|
|
241
|
+
// it could result in a bug.
|
|
242
|
+
//
|
|
243
|
+
// Consider a type of `useLayoutEffect()` that skips the initial mount:
|
|
244
|
+
// `useLayoutEffectSkipInitialMount()`.
|
|
245
|
+
// Suppose that effect is written in such a way that it only skips the first call of itself.
|
|
246
|
+
// In that case, if React is run in "strict" mode, the effect will no longer work as expected
|
|
247
|
+
// and it won't actually skip the initial mount and will be executed during the second initial run.
|
|
248
|
+
// But the `VirtualScroller` itself has already implemented a workaround that prevents
|
|
249
|
+
// its hooks from running twice on mount. This means that `useVirtualScrollerStartStop()`
|
|
250
|
+
// of the React component would have already stopped the `VirtualScroller` by the time
|
|
251
|
+
// `ItemComponent`'s incorrectly-behaving `useLayoutEffectSkipInitialMount()` effect is run,
|
|
252
|
+
// resulting in an error: "`VirtualScroller` hasn't been started".
|
|
253
|
+
//
|
|
254
|
+
// The log when not in "strict" mode would be:
|
|
255
|
+
//
|
|
256
|
+
// * `useLayoutEffect()` is run in `ItemComponent` — skips the initial run.
|
|
257
|
+
// * `useLayoutEffect()` is run in `useVirtualScrollerStartStop()`. It starts the `VirtualScroller`.
|
|
258
|
+
// * Some dependency property gets updated inside `ItemComponent`.
|
|
259
|
+
// * `useLayoutEffect()` is run in `ItemComponent` — no longer skips. Calls `setItemState()`.
|
|
260
|
+
// * The `VirtualScroller` is started so it handles `setState()` correctly.
|
|
261
|
+
//
|
|
262
|
+
// The log when in "strict" mode would be:
|
|
263
|
+
//
|
|
264
|
+
// * `useLayoutEffect()` is run in `ItemComponent` — skips the initial run.
|
|
265
|
+
// * `useLayoutEffect()` is run in `useVirtualScrollerStartStop()`. It starts the `VirtualScroller`.
|
|
266
|
+
// * `useLayoutEffect()` is unmounted in `useVirtualScrollerStartStop()`. It stops the `VirtualScroller`.
|
|
267
|
+
// * `useLayoutEffect()` is unmounted in `ItemComponent` — does nothing.
|
|
268
|
+
// * `useLayoutEffect()` is run the second time in `ItemComponent` — no longer skips. Calls `setItemState()`.
|
|
269
|
+
// * The `VirtualScroller` is stopped so it throws an error: "`VirtualScroller` hasn't been started".
|
|
270
|
+
//
|
|
271
|
+
// For that reason, the requirement of the `VirtualScroller` to be started was commented out.
|
|
272
|
+
// Commenting it out wouldn't result in any potential bugs because the code would work correctly
|
|
273
|
+
// in both cases.
|
|
274
|
+
// this.hasToBeStarted()
|
|
235
275
|
this._setItemState(i, newItemState)
|
|
236
276
|
}
|
|
237
277
|
|
|
@@ -441,9 +441,13 @@ export default function() {
|
|
|
441
441
|
if (previousHeight !== newHeight) {
|
|
442
442
|
log('~ Item height has changed. Should update layout. ~')
|
|
443
443
|
|
|
444
|
-
// Update or reset a previously calculated layout
|
|
445
|
-
// so that the "diff"s based on that
|
|
446
|
-
//
|
|
444
|
+
// Update or reset a previously calculated layout with the new item height
|
|
445
|
+
// so that the potential future "diff"s based on that "previously calculated" layout
|
|
446
|
+
// would be correct.
|
|
447
|
+
//
|
|
448
|
+
// The "previously calculated layout" feature is not currently used
|
|
449
|
+
// so this function call doesn't really affect anything.
|
|
450
|
+
//
|
|
447
451
|
updatePreviouslyCalculatedLayoutOnItemHeightChange.call(this, i, previousHeight, newHeight)
|
|
448
452
|
|
|
449
453
|
// Recalculate layout.
|
|
@@ -455,11 +459,13 @@ export default function() {
|
|
|
455
459
|
// that might happen in the middle of the currently pending `setState()` operation
|
|
456
460
|
// being applied, resulting in weird "race condition" bugs.
|
|
457
461
|
//
|
|
458
|
-
if (this.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
462
|
+
if (this._isActive) {
|
|
463
|
+
if (this.waitingForRender) {
|
|
464
|
+
log('~ Another state update is already waiting to be rendered. Delay the layout update until then. ~')
|
|
465
|
+
this.updateLayoutAfterRenderBecauseItemHeightChanged = true
|
|
466
|
+
} else {
|
|
467
|
+
this.onUpdateShownItemIndexes({ reason: LAYOUT_REASON.ITEM_HEIGHT_CHANGED })
|
|
468
|
+
}
|
|
463
469
|
}
|
|
464
470
|
|
|
465
471
|
// If there was a request for `setState()` with new `items`, then the changes
|
|
@@ -63,7 +63,7 @@ export default function() {
|
|
|
63
63
|
// would construct its own state object.
|
|
64
64
|
if (!shallowEqual(newState, this.mostRecentSetStateValue)) {
|
|
65
65
|
warn('The most recent state that was set', getStateSnapshot(this.mostRecentSetStateValue))
|
|
66
|
-
reportError('
|
|
66
|
+
reportError('`VirtualScroller` has been rendered with a `state` that is not equal to the most recently set one')
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -199,7 +199,7 @@ export default function() {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
if (!this._isActive) {
|
|
202
|
-
this.
|
|
202
|
+
this._afterRenderStateUpdateThatWasStopped = stateUpdate
|
|
203
203
|
return
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -13,6 +13,8 @@ import useUpdateItemKeysOnItemsChange from './useUpdateItemKeysOnItemsChange.js'
|
|
|
13
13
|
import useClassName from './useClassName.js'
|
|
14
14
|
import useStyle from './useStyle.js'
|
|
15
15
|
|
|
16
|
+
import { warn } from '../utility/debug.js'
|
|
17
|
+
|
|
16
18
|
// When `items` property changes:
|
|
17
19
|
// * A new `items` property is supplied to the React component.
|
|
18
20
|
// * The React component re-renders itself.
|
|
@@ -172,6 +174,7 @@ function VirtualScroller({
|
|
|
172
174
|
// `onMount()` option is deprecated due to no longer being used.
|
|
173
175
|
// If someone thinks there's a valid use case for it, create an issue.
|
|
174
176
|
if (onMount) {
|
|
177
|
+
warn('`onMount` property is deprecated')
|
|
175
178
|
onMount()
|
|
176
179
|
}
|
|
177
180
|
}, [])
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
4
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
5
|
+
// https://github.com/facebook/react/issues/26320
|
|
6
|
+
export default function useEffectDontMountTwiceInStrictMode(useEffect, handler, dependencies) {
|
|
7
|
+
if (!Array.isArray(dependencies)) {
|
|
8
|
+
throw new Error('Dependencies argument must be an array')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { onEffect } = useEffectStatus()
|
|
12
|
+
const { onChange } = usePrevousValue(dependencies)
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const { isInitialRun } = onEffect()
|
|
16
|
+
const previousDependencies = onChange(dependencies)
|
|
17
|
+
if (isInitialRun || !isShallowEqualArrays(previousDependencies, dependencies)) {
|
|
18
|
+
const cleanUpFunction = handler()
|
|
19
|
+
if (typeof cleanUpFunction === 'function') {
|
|
20
|
+
throw new Error('An effect can\'t return a clean-up function when used with `useEffectDontMountTwiceInStrictMode()` because the clean-up function won\'t behave correctly in that case')
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, dependencies)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function useEffectStatus() {
|
|
27
|
+
const hasMounted = useRef(false)
|
|
28
|
+
|
|
29
|
+
const onEffect = useCallback(() => {
|
|
30
|
+
const wasAlreadyMounted = hasMounted.current
|
|
31
|
+
hasMounted.current = true
|
|
32
|
+
return {
|
|
33
|
+
isInitialRun: !wasAlreadyMounted
|
|
34
|
+
}
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
onEffect
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function usePrevousValue(value) {
|
|
43
|
+
const prevValue = useRef(value)
|
|
44
|
+
|
|
45
|
+
const onChange = useCallback((value) => {
|
|
46
|
+
const previousValue = prevValue.current
|
|
47
|
+
prevValue.current = value
|
|
48
|
+
return previousValue
|
|
49
|
+
}, [])
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
onChange
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isShallowEqualArrays(a, b) {
|
|
57
|
+
if (a.length !== b.length) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
let i = 0
|
|
61
|
+
while (i < a.length) {
|
|
62
|
+
if (a[i] !== b[i]) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
i++
|
|
66
|
+
}
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useInsertionEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'
|
|
4
|
+
|
|
5
|
+
// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
6
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
7
|
+
// https://github.com/facebook/react/issues/26320
|
|
8
|
+
export default function useInsertionEffectDontMountTwiceInStrictMode(handler, dependencies) {
|
|
9
|
+
return useEffectDontMountTwiceInStrictMode(useInsertionEffect, handler, dependencies)
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'
|
|
4
|
+
|
|
5
|
+
// A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
|
|
6
|
+
// in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
|
|
7
|
+
// https://github.com/facebook/react/issues/26320
|
|
8
|
+
export default function useLayoutEffectDontMountTwiceInStrictMode(handler, dependencies) {
|
|
9
|
+
return useEffectDontMountTwiceInStrictMode(useLayoutEffect, handler, dependencies)
|
|
10
|
+
}
|
package/source/react/useState.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import log, { isDebug } from '../utility/debug.js'
|
|
2
2
|
import getStateSnapshot from '../utility/getStateSnapshot.js'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { useRef, useCallback } from 'react'
|
|
5
|
+
import useStateNoStaleBug from './useStateNoStaleBug.js'
|
|
6
|
+
import useInsertionEffectDontMountTwiceInStrictMode from './useInsertionEffectDontMountTwiceInStrictMode.js'
|
|
7
|
+
import useLayoutEffectDontMountTwiceInStrictMode from './useLayoutEffectDontMountTwiceInStrictMode.js'
|
|
5
8
|
|
|
6
9
|
// Creates state management functions.
|
|
7
10
|
export default function _useState({
|
|
@@ -14,7 +17,7 @@ export default function _useState({
|
|
|
14
17
|
// `VirtualScroller` state gets updated from this variable.
|
|
15
18
|
// The reason for that is that `VirtualScroller` state must always
|
|
16
19
|
// correspond exactly to what's currently rendered on the screen.
|
|
17
|
-
const [_newState, _setNewState] =
|
|
20
|
+
const [_newState, _setNewState] = useStateNoStaleBug(initialState)
|
|
18
21
|
|
|
19
22
|
// This `state` reference is what `VirtualScroller` uses internally.
|
|
20
23
|
// It's the "source of truth" on the actual `VirtualScroller` state.
|
|
@@ -137,7 +140,7 @@ export default function _useState({
|
|
|
137
140
|
// So if `useInsertionEffect()` gets removed from React in some hypothetical future,
|
|
138
141
|
// it could be replaced with using `ref`s on `ItemComponent`s to measure the DOM element heights.
|
|
139
142
|
//
|
|
140
|
-
|
|
143
|
+
useInsertionEffectDontMountTwiceInStrictMode(() => {
|
|
141
144
|
// Update the actual `VirtualScroller` state right before the DOM changes
|
|
142
145
|
// are going to be applied for the requested state update.
|
|
143
146
|
//
|
|
@@ -160,13 +163,13 @@ export default function _useState({
|
|
|
160
163
|
// This hook doesn't do anything at the initial render.
|
|
161
164
|
//
|
|
162
165
|
if (isDebug()) {
|
|
163
|
-
log('React: ~ The requested state is about to be applied in DOM.
|
|
166
|
+
log('React: ~ The requested state is about to be applied in DOM. Setting it as the `VirtualScroller` state. ~')
|
|
164
167
|
log(getStateSnapshot(_newState))
|
|
165
168
|
}
|
|
166
169
|
setState(_newState)
|
|
167
170
|
}, [_newState])
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
useLayoutEffectDontMountTwiceInStrictMode(() => {
|
|
170
173
|
// Call `onRender()` right after a requested state update has been applied,
|
|
171
174
|
// and also right after the initial render.
|
|
172
175
|
onRender()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useRef, useState, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
// This hook fixes any weird intermediate inconsistent/invalid/stale state values.
|
|
4
|
+
// https://github.com/facebook/react/issues/25023#issuecomment-1480463544
|
|
5
|
+
export default function useStateNoStaleBug(initialState) {
|
|
6
|
+
// const latestValidState = useRef(initialState)
|
|
7
|
+
const latestWrittenState = useRef(initialState)
|
|
8
|
+
const [_state, _setState] = useState(initialState)
|
|
9
|
+
|
|
10
|
+
// Instead of dealing with a potentially out-of-sync (stale) state value,
|
|
11
|
+
// simply use the correct latest one.
|
|
12
|
+
const state = latestWrittenState.current
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
let state
|
|
16
|
+
if (_state === latestWrittenState.current) {
|
|
17
|
+
state = _state
|
|
18
|
+
latestValidState.current = _state
|
|
19
|
+
} else {
|
|
20
|
+
// React bug detected: an out-of-sync (stale) state value received.
|
|
21
|
+
// Ignore the out-of-sync (stale) state value.
|
|
22
|
+
state = latestValidState.current
|
|
23
|
+
}
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const setState = useCallback((newState) => {
|
|
27
|
+
if (typeof newState === 'function') {
|
|
28
|
+
throw new Error('Function argument of `setState()` function is not supported by this hook')
|
|
29
|
+
}
|
|
30
|
+
latestWrittenState.current = newState
|
|
31
|
+
_setState(newState)
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
return [state, setState]
|
|
35
|
+
}
|