virtual-scroller 1.15.0 → 1.15.1

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 (77) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +98 -8
  3. package/bundle/index-dom-bypass.html +3 -4
  4. package/bundle/index-dom-grid.html +3 -4
  5. package/bundle/index-dom-scrollableContainer.html +3 -4
  6. package/bundle/index-dom.html +3 -4
  7. package/bundle/index-react-bypass.html +56 -62
  8. package/bundle/index-react-grid.html +55 -61
  9. package/bundle/index-react-hook.html +209 -0
  10. package/bundle/index-react-scrollableContainer.html +56 -62
  11. package/bundle/index-react-strictMode.html +55 -61
  12. package/bundle/index-react-tbody-scrollableContainer.html +13 -15
  13. package/bundle/index-react-tbody.html +10 -12
  14. package/bundle/virtual-scroller-dom.js +1 -1
  15. package/bundle/virtual-scroller-dom.js.map +1 -1
  16. package/bundle/virtual-scroller-react.js +1 -1
  17. package/bundle/virtual-scroller-react.js.map +1 -1
  18. package/bundle/virtual-scroller.js +1 -1
  19. package/bundle/virtual-scroller.js.map +1 -1
  20. package/commonjs/react/VirtualScroller.js +69 -127
  21. package/commonjs/react/VirtualScroller.js.map +1 -1
  22. package/commonjs/react/useCreateVirtualScroller.js +64 -0
  23. package/commonjs/react/useCreateVirtualScroller.js.map +1 -0
  24. package/commonjs/react/useInstanceMethods.js +2 -2
  25. package/commonjs/react/useInstanceMethods.js.map +1 -1
  26. package/commonjs/react/useMergeRefs.js +52 -0
  27. package/commonjs/react/useMergeRefs.js.map +1 -0
  28. package/commonjs/react/{useVirtualScrollerStartStop.js → useStartStopVirtualScroller.js} +1 -1
  29. package/commonjs/react/{useVirtualScrollerStartStop.js.map → useStartStopVirtualScroller.js.map} +1 -1
  30. package/commonjs/react/useStyle.js +18 -0
  31. package/commonjs/react/useStyle.js.map +1 -1
  32. package/commonjs/react/useVirtualScroller.js +142 -43
  33. package/commonjs/react/useVirtualScroller.js.map +1 -1
  34. package/modules/react/VirtualScroller.js +68 -119
  35. package/modules/react/VirtualScroller.js.map +1 -1
  36. package/modules/react/useCreateVirtualScroller.js +53 -0
  37. package/modules/react/useCreateVirtualScroller.js.map +1 -0
  38. package/modules/react/useInstanceMethods.js +2 -2
  39. package/modules/react/useInstanceMethods.js.map +1 -1
  40. package/modules/react/useMergeRefs.js +44 -0
  41. package/modules/react/useMergeRefs.js.map +1 -0
  42. package/modules/react/{useVirtualScrollerStartStop.js → useStartStopVirtualScroller.js} +1 -1
  43. package/modules/react/{useVirtualScrollerStartStop.js.map → useStartStopVirtualScroller.js.map} +1 -1
  44. package/modules/react/useStyle.js +17 -0
  45. package/modules/react/useStyle.js.map +1 -1
  46. package/modules/react/useVirtualScroller.js +136 -43
  47. package/modules/react/useVirtualScroller.js.map +1 -1
  48. package/package.json +4 -1
  49. package/react/index.cjs +2 -1
  50. package/react/index.d.ts +51 -7
  51. package/react/index.js +1 -0
  52. package/rollup.config.mjs +15 -1
  53. package/source/react/VirtualScroller.js +66 -127
  54. package/source/react/useCreateVirtualScroller.js +65 -0
  55. package/source/react/useInstanceMethods.js +2 -2
  56. package/source/react/useMergeRefs.js +45 -0
  57. package/source/react/useStyle.js +15 -0
  58. package/source/react/useVirtualScroller.js +155 -48
  59. package/website/index-dom-bypass.html +3 -4
  60. package/website/index-dom-grid.html +3 -4
  61. package/website/index-dom-scrollableContainer.html +3 -4
  62. package/website/index-dom.html +3 -4
  63. package/website/index-react-bypass.html +56 -62
  64. package/website/index-react-grid.html +55 -61
  65. package/website/index-react-hook.html +209 -0
  66. package/website/index-react-scrollableContainer.html +56 -62
  67. package/website/index-react-strictMode.html +55 -61
  68. package/website/index-react-tbody-scrollableContainer.html +13 -15
  69. package/website/index-react-tbody.html +10 -12
  70. package/website/index-react.html +55 -61
  71. package/website/index.html +55 -61
  72. package/commonjs/react/useForwardedRef.js +0 -50
  73. package/commonjs/react/useForwardedRef.js.map +0 -1
  74. package/modules/react/useForwardedRef.js +0 -42
  75. package/modules/react/useForwardedRef.js.map +0 -1
  76. package/source/react/useForwardedRef.js +0 -39
  77. /package/source/react/{useVirtualScrollerStartStop.js → useStartStopVirtualScroller.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  <!-- `virtual-scroller`: in `.updateItems()` handle a case when `items.length` is the same, in which case find different items and if those items are rendered then maybe update them on screen and update their height, if the items are past rendered then maybe just discard all item heights past rendered, if the items are before rendered then maybe ignore and it will jump on scroll up which is kinda acceptable. -->
2
2
 
3
+ 1.15.1 / 30.11.2025
4
+ ===================
5
+
6
+ * Added `useVirtualScroller()` hook that is exported from `virtual-scroller/react` subpackage.
7
+ * Un-deprecated `tbody: boolean` property because it's used in the new hook.
8
+
3
9
  1.15.0 / 30.11.2025
4
10
  ===================
5
11
 
package/README.md CHANGED
@@ -29,6 +29,8 @@ A universal open-source implementation of Twitter's [`VirtualScroller`](https://
29
29
  * [Table in a scrollable container](https://catamphetamine.gitlab.io/virtual-scroller/index-react-tbody-scrollableContainer.html)
30
30
  * [Grid](https://catamphetamine.gitlab.io/virtual-scroller/index-react-grid.html)
31
31
  * [Paginated Grid](https://catamphetamine.gitlab.io/virtual-scroller/index-react-grid.html?pagination=✓)
32
+ * [List (using hook)](https://catamphetamine.gitlab.io/virtual-scroller/index-react-hook.html)
33
+ * [Paginated List (using hook)](https://catamphetamine.gitlab.io/virtual-scroller/index-react-hook.html?pagination=✓)
32
34
 
33
35
  ## Rationale
34
36
 
@@ -172,6 +174,18 @@ function App() {
172
174
 
173
175
  <!-- Note: When passing any core `VirtualScroller` class options, only the initial values of those options will be applied, and any updates to those options will be ignored. That's because those options are only passed to the `VirtualScroller` base class constructor at initialization time. That means that none of those options should depend on any variable state or props. For example, if `getColumnsCount()` parameter was defined as `() => props.columnsCount`, then, if the `columnsCount` property changes, the underlying `VirtualScroller` instance won't see that change. -->
174
176
 
177
+ <!-- I guess that `style` property should be deprecated because it could potentially be dangerous
178
+ due to potential conflicts with `VirtualScroller` styles. It currently isn't present anyway. -->
179
+ <!-- * `style: object` — Custom CSS style, except for `padding-top` or `padding-bottom`. -->
180
+
181
+ <!-- I guess that `className` property should be deprecated because it could potentially be dangerous
182
+ due to potential conflicts with `VirtualScroller` styles. -->
183
+ <!-- * `className: string` — Custom CSS class name. -->
184
+
185
+ * `tbody: boolean` — When the list items container element is going to be a `<tbody/>`, it will have to use a special workaround in order for the `<VirtualScroller/>` to work correctly. To enable this special workaround, a developer could pass a `tbody: true` property. Otherwise, `<VirtualScroller/>` will only enable it when `itemsContainerComponent === "tbody"`.
186
+ <!-- * There's no longer such option in the ["core"](#core) component because it's autodetected there. The reason why it can't always be autodetected in React is because of server-side rendering when there's no items container DOM element whose tag name could be examined to detect the use of a `<tbody/>` tag as an items container. -->
187
+ * Only the initial value of this property is used, and any changes to it will be ignored.
188
+
175
189
  * `getColumnsCount(): number` — Returns the count of the columns.
176
190
  * This is simply a proxy for the ["core"](#core) component's `getColumnsCount` [option](#options).
177
191
  * Only the initial value of this property is used, and any changes to it will be ignored.
@@ -277,14 +291,6 @@ function ListContainer() {
277
291
  }
278
292
  ```
279
293
 
280
- <!--
281
- (this property has been ignored for a long time and was eventually removed)
282
-
283
- * `tbody: boolean` — When the container for the list items is going to be a `<tbody/>`, a developer must pass a `tbody: true` property in order for the `<VirtualScroller/>` to work correctly.
284
- * This is simply a proxy for the ["core"](#core) component's `tbody` option.
285
- * Only the initial value of this property is used, and any changes to it will be ignored.
286
- -->
287
-
288
294
  * `itemsContainerComponentRef: object` — Could be used to get access to the `itemsContainerComponent` instance.
289
295
  * For example, if `itemsContainerComponent` is `"ul"` then `itemsContainerComponentRef.current` will be set to the `<ul/>` `Element`.
290
296
 
@@ -475,6 +481,90 @@ By default, on server side, it will just render the first item, as if the list o
475
481
  To fix that, a developer should specify certain properties — `getEstimatedVisibleItemRowsCount(): number` and `getEstimatedItemHeight(): number` and `getEstimatedInterItemVerticalSpacing(): number` — so that it could calculate how many items it should render and how much space it should leave for scrolling. For more technical details, see the description of these parameters in the ["core"](#core) component's [options](#options).
476
482
  </details>
477
483
 
484
+ ######
485
+
486
+ <details>
487
+ <summary>Alternatively, instead of using <code>&lt;VirtualScroller/&gt;</code> component, one could use <code>useVirtualScroller()</code> hook</summary>
488
+
489
+ ######
490
+
491
+ ```js
492
+ import React from 'react'
493
+ import { useVirtualScroller } from 'virtual-scroller/react'
494
+
495
+ function List(props) {
496
+ const {
497
+ // "Core" component `state`.
498
+ // See "State" section of the readme for more info.
499
+ state: {
500
+ items,
501
+ itemStates,
502
+ firstShownItemIndex,
503
+ lastShownItemIndex
504
+ },
505
+ // CSS style object.
506
+ style,
507
+ // CSS class name.
508
+ className,
509
+ // This `ref` must be passed to the items container component.
510
+ itemsContainerRef,
511
+ // One could use this `virtualScroller` object to call any of its public methods.
512
+ // Except for `virtualScroller.getState()` — use the returned `state` property instead.
513
+ virtualScroller
514
+ } = useVirtualScroller({
515
+ // The properties of `useVirtualScroller()` hook are the same as
516
+ // the properties of `<VirtualScroller/>` component.
517
+ //
518
+ // Additional properties:
519
+ // * `style`
520
+ // * `className`
521
+ //
522
+ // Excluded properties:
523
+ // * `itemComponent`
524
+ // * `itemComponentProps`
525
+ // * `itemsContainerComponent`
526
+ // * `itemsContainerComponentProps`
527
+ //
528
+ items: props.items
529
+ })
530
+
531
+ return (
532
+ <div ref={itemsContainerRef} style={style} className={className}>
533
+ {items.map((item, i) => {
534
+ if (i >= firstShownItemIndex && i <= lastShownItemIndex) {
535
+ return (
536
+ <ListItem
537
+ key={item.id}
538
+ item={item}
539
+ state={itemStates && itemStates[i]}
540
+ />
541
+ )
542
+ }
543
+ return null
544
+ })}
545
+ </div>
546
+ )
547
+ }
548
+
549
+ function ListItem({ item, state }) {
550
+ const { username, date, text } = item
551
+ return (
552
+ <article>
553
+ <a href={`/users/${username}`}>
554
+ @{username}
555
+ </a>
556
+ <time dateTime={date.toISOString()}>
557
+ {date.toString()}
558
+ </time>
559
+ <p>
560
+ {text}
561
+ </p>
562
+ </article>
563
+ )
564
+ }
565
+ ```
566
+ </details>
567
+
478
568
  ## DOM
479
569
 
480
570
  `virtual-scroller/dom` exports a `VirtualScroller` class that implements a "virtual scroller" in a standard [Document Object Model](https://en.wikipedia.org/wiki/Document_Object_Model) environment such as a web browser.
@@ -72,6 +72,7 @@
72
72
  const { toIndex } = getState()
73
73
  fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
74
74
  setState({
75
+ ...getState(),
75
76
  fromIndex,
76
77
  items: items.slice(fromIndex, toIndex + 1)
77
78
  })
@@ -82,6 +83,7 @@
82
83
  let { toIndex } = getState()
83
84
  toIndex = Math.min(toIndex + BATCH_SIZE, items.length - 1)
84
85
  setState({
86
+ ...getState(),
85
87
  toIndex,
86
88
  items: items.slice(fromIndex, toIndex + 1)
87
89
  })
@@ -159,10 +161,7 @@
159
161
 
160
162
  setState = (newState) => {
161
163
  const prevState = this.state
162
- this.state = {
163
- ...this.state,
164
- ...newState
165
- }
164
+ this.state = newState
166
165
  this.onStateChange(this.state, prevState)
167
166
  }
168
167
 
@@ -78,6 +78,7 @@
78
78
  const { toIndex } = getState()
79
79
  fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
80
80
  setState({
81
+ ...getState(),
81
82
  fromIndex,
82
83
  items: items.slice(fromIndex, toIndex + 1)
83
84
  })
@@ -88,6 +89,7 @@
88
89
  let { toIndex } = getState()
89
90
  toIndex = Math.min(toIndex + BATCH_SIZE, items.length - 1)
90
91
  setState({
92
+ ...getState(),
91
93
  toIndex,
92
94
  items: items.slice(fromIndex, toIndex + 1)
93
95
  })
@@ -165,10 +167,7 @@
165
167
 
166
168
  setState = (newState) => {
167
169
  const prevState = this.state
168
- this.state = {
169
- ...this.state,
170
- ...newState
171
- }
170
+ this.state = newState
172
171
  this.onStateChange(this.state, prevState)
173
172
  }
174
173
 
@@ -85,6 +85,7 @@
85
85
  const { toIndex } = getState()
86
86
  fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
87
87
  setState({
88
+ ...getState(),
88
89
  fromIndex,
89
90
  items: items.slice(fromIndex, toIndex + 1)
90
91
  })
@@ -95,6 +96,7 @@
95
96
  let { toIndex } = getState()
96
97
  toIndex = Math.min(toIndex + BATCH_SIZE, items.length - 1)
97
98
  setState({
99
+ ...getState(),
98
100
  toIndex,
99
101
  items: items.slice(fromIndex, toIndex + 1)
100
102
  })
@@ -176,10 +178,7 @@
176
178
 
177
179
  setState = (newState) => {
178
180
  const prevState = this.state
179
- this.state = {
180
- ...this.state,
181
- ...newState
182
- }
181
+ this.state = newState
183
182
  this.onStateChange(this.state, prevState)
184
183
  }
185
184
 
@@ -72,6 +72,7 @@
72
72
  const { toIndex } = getState()
73
73
  fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
74
74
  setState({
75
+ ...getState(),
75
76
  fromIndex,
76
77
  items: items.slice(fromIndex, toIndex + 1)
77
78
  })
@@ -82,6 +83,7 @@
82
83
  let { toIndex } = getState()
83
84
  toIndex = Math.min(toIndex + BATCH_SIZE, items.length - 1)
84
85
  setState({
86
+ ...getState(),
85
87
  toIndex,
86
88
  items: items.slice(fromIndex, toIndex + 1)
87
89
  })
@@ -159,10 +161,7 @@
159
161
 
160
162
  setState = (newState) => {
161
163
  const prevState = this.state
162
- this.state = {
163
- ...this.state,
164
- ...newState
165
- }
164
+ this.state = newState
166
165
  this.onStateChange(this.state, prevState)
167
166
  }
168
167
 
@@ -64,6 +64,7 @@
64
64
  const { toIndex } = getState()
65
65
  fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
66
66
  setState({
67
+ ...getState(),
67
68
  fromIndex,
68
69
  items: items.slice(fromIndex, toIndex + 1)
69
70
  })
@@ -74,64 +75,59 @@
74
75
  let { toIndex } = getState()
75
76
  toIndex = Math.min(toIndex + BATCH_SIZE, items.length - 1)
76
77
  setState({
78
+ ...getState(),
77
79
  toIndex,
78
80
  items: items.slice(fromIndex, toIndex + 1)
79
81
  })
80
82
  }
81
83
 
82
- class VirtualScrollerDemo extends React.Component {
83
- constructor(props) {
84
- super(props)
84
+ function VirtualScrollerDemo() {
85
+ const [state, setState] = React.useState(getInitialState(ITEMS))
85
86
 
86
- this.state = getInitialState(ITEMS)
87
- }
88
-
89
- getState = () => this.state
87
+ const getState = () => state
90
88
 
91
- onShowPrevious = () => {
92
- onShowPrevious(ITEMS, this.getState, this.setState.bind(this))
89
+ const onShowPrevious_ = () => {
90
+ onShowPrevious(ITEMS, getState, setState)
93
91
  }
94
92
 
95
- onShowNext = () => {
96
- onShowNext(ITEMS, this.getState, this.setState.bind(this))
93
+ const onShowNext_ = () => {
94
+ onShowNext(ITEMS, getState, setState)
97
95
  }
98
96
 
99
- render() {
100
- const {
101
- fromIndex,
102
- toIndex,
103
- items
104
- } = this.state
105
-
106
- return (
107
- <React.Fragment>
108
- {window.PAGINATION && fromIndex > 0 &&
109
- <button
110
- type="button"
111
- onClick={this.onShowPrevious}
112
- className="load-items-button">
113
- Show previous
114
- </button>
115
- }
116
- <VirtualScroller
117
- bypass
118
- id="list"
119
- items={items}
120
- itemComponent={Item}
121
- preserveScrollPositionOnPrependItems
122
- getColumnsCount={getColumnsCount}
123
- />
124
- {window.PAGINATION && toIndex < ITEMS.length - 1 &&
125
- <button
126
- type="button"
127
- onClick={this.onShowNext}
128
- className="load-items-button">
129
- Show next
130
- </button>
131
- }
132
- </React.Fragment>
133
- )
134
- }
97
+ const {
98
+ fromIndex,
99
+ toIndex,
100
+ items
101
+ } = state
102
+
103
+ return (
104
+ <React.Fragment>
105
+ {window.PAGINATION && fromIndex > 0 &&
106
+ <button
107
+ type="button"
108
+ onClick={onShowPrevious_}
109
+ className="load-items-button">
110
+ Show previous
111
+ </button>
112
+ }
113
+ <VirtualScroller
114
+ bypass
115
+ id="list"
116
+ items={items}
117
+ itemComponent={Item}
118
+ preserveScrollPositionOnPrependItems
119
+ getColumnsCount={getColumnsCount}
120
+ />
121
+ {window.PAGINATION && toIndex < ITEMS.length - 1 &&
122
+ <button
123
+ type="button"
124
+ onClick={onShowNext_}
125
+ className="load-items-button">
126
+ Show next
127
+ </button>
128
+ }
129
+ </React.Fragment>
130
+ )
135
131
  }
136
132
 
137
133
  const item = PropTypes.shape({
@@ -168,21 +164,19 @@
168
164
  children: item.isRequired
169
165
  }
170
166
 
171
- class Demo extends React.Component {
172
- render() {
173
- return (
174
- <section className="container">
175
- <h1>
176
- <TwitterLogo/>
177
- Latest Tweets on #politics
178
- </h1>
179
- <VirtualScrollerDemo/>
180
- <footer>
181
- © Twitter Inc., 2019
182
- </footer>
183
- </section>
184
- )
185
- }
167
+ function Demo() {
168
+ return (
169
+ <section className="container">
170
+ <h1>
171
+ <TwitterLogo/>
172
+ Latest Tweets on #politics
173
+ </h1>
174
+ <VirtualScrollerDemo/>
175
+ <footer>
176
+ © Twitter Inc., 2019
177
+ </footer>
178
+ </section>
179
+ )
186
180
  }
187
181
 
188
182
  function TwitterLogo() {
@@ -68,6 +68,7 @@
68
68
  const { toIndex } = getState()
69
69
  fromIndex = Math.max(fromIndex - BATCH_SIZE, 0)
70
70
  setState({
71
+ ...getState(),
71
72
  fromIndex,
72
73
  items: items.slice(fromIndex, toIndex + 1)
73
74
  })
@@ -78,63 +79,58 @@
78
79
  let { toIndex } = getState()
79
80
  toIndex = Math.min(toIndex + BATCH_SIZE, items.length - 1)
80
81
  setState({
82
+ ...getState(),
81
83
  toIndex,
82
84
  items: items.slice(fromIndex, toIndex + 1)
83
85
  })
84
86
  }
85
87
 
86
- class VirtualScrollerDemo extends React.Component {
87
- constructor(props) {
88
- super(props)
88
+ function VirtualScrollerDemo() {
89
+ const [state, setState] = React.useState(getInitialState(ITEMS))
89
90
 
90
- this.state = getInitialState(ITEMS)
91
- }
92
-
93
- getState = () => this.state
91
+ const getState = () => state
94
92
 
95
- onShowPrevious = () => {
96
- onShowPrevious(ITEMS, this.getState, this.setState.bind(this))
93
+ const onShowPrevious_ = () => {
94
+ onShowPrevious(ITEMS, getState, setState)
97
95
  }
98
96
 
99
- onShowNext = () => {
100
- onShowNext(ITEMS, this.getState, this.setState.bind(this))
97
+ const onShowNext_ = () => {
98
+ onShowNext(ITEMS, getState, setState)
101
99
  }
102
100
 
103
- render() {
104
- const {
105
- fromIndex,
106
- toIndex,
107
- items
108
- } = this.state
109
-
110
- return (
111
- <React.Fragment>
112
- {window.PAGINATION && fromIndex > 0 &&
113
- <button
114
- type="button"
115
- onClick={this.onShowPrevious}
116
- className="load-items-button">
117
- Show previous
118
- </button>
119
- }
120
- <VirtualScroller
121
- id="list"
122
- items={items}
123
- itemComponent={Item}
124
- preserveScrollPositionOnPrependItems
125
- getColumnsCount={getColumnsCount}
126
- />
127
- {window.PAGINATION && toIndex < ITEMS.length - 1 &&
128
- <button
129
- type="button"
130
- onClick={this.onShowNext}
131
- className="load-items-button">
132
- Show next
133
- </button>
134
- }
135
- </React.Fragment>
136
- )
137
- }
101
+ const {
102
+ fromIndex,
103
+ toIndex,
104
+ items
105
+ } = state
106
+
107
+ return (
108
+ <React.Fragment>
109
+ {window.PAGINATION && fromIndex > 0 &&
110
+ <button
111
+ type="button"
112
+ onClick={onShowPrevious_}
113
+ className="load-items-button">
114
+ Show previous
115
+ </button>
116
+ }
117
+ <VirtualScroller
118
+ id="list"
119
+ items={items}
120
+ itemComponent={Item}
121
+ preserveScrollPositionOnPrependItems
122
+ getColumnsCount={getColumnsCount}
123
+ />
124
+ {window.PAGINATION && toIndex < ITEMS.length - 1 &&
125
+ <button
126
+ type="button"
127
+ onClick={onShowNext_}
128
+ className="load-items-button">
129
+ Show next
130
+ </button>
131
+ }
132
+ </React.Fragment>
133
+ )
138
134
  }
139
135
 
140
136
  const item = PropTypes.shape({
@@ -171,21 +167,19 @@
171
167
  children: item.isRequired
172
168
  }
173
169
 
174
- class Demo extends React.Component {
175
- render() {
176
- return (
177
- <section className="container">
178
- <h1>
179
- <TwitterLogo/>
180
- Latest Tweets on #politics
181
- </h1>
182
- <VirtualScrollerDemo/>
183
- <footer>
184
- © Twitter Inc., 2019
185
- </footer>
186
- </section>
187
- )
188
- }
170
+ function Demo() {
171
+ return (
172
+ <section className="container">
173
+ <h1>
174
+ <TwitterLogo/>
175
+ Latest Tweets on #politics
176
+ </h1>
177
+ <VirtualScrollerDemo/>
178
+ <footer>
179
+ © Twitter Inc., 2019
180
+ </footer>
181
+ </section>
182
+ )
189
183
  }
190
184
 
191
185
  function TwitterLogo() {