ListView
ListView renders large lists efficiently using virtualization. Only visible items are rendered. Maps to UI Toolkit's ListView.
Import
Copy import { ListView } from "onejs-react"
Basic Usage
Copy const items = [ "Apple" , "Banana" , "Cherry" , "Date" , "Elderberry" ]
function FruitList () {
return (
< ListView
itemsSource = {items}
fixedItemHeight = { 40 }
makeItem = {() => new CS .UnityEngine.UIElements. Label ()}
bindItem = {( element , index ) => {
element.text = items[index]
}}
style = {{ height: 300 }}
/>
)
}
Props
Required Props
Prop Type Description itemsSourceunknown[]Array of items to display makeItem() => VisualElementFactory function to create elements bindItem(element, index) => voidFunction to populate element with data
Optional Props
Prop Type Default Description unbindItem(element, index) => voidCalled when element is recycled destroyItem(element) => voidCalled when element is destroyed fixedItemHeightnumberFixed height for all items virtualizationMethod"FixedHeight", "DynamicHeight""FixedHeight"Virtualization strategy
Selection Props
Prop Type Default Description selectionType"None", "Single", "Multiple""Single"Selection mode selectedIndexnumberCurrently selected index selectedIndicesnumber[]Selected indices (multiple) onSelectionChange(indices: number[]) => voidSelection change callback onItemsChosen(items: unknown[]) => voidDouble-click/Enter callback
Reordering Props
Prop Type Default Description reorderablebooleanfalseEnable drag reordering reorderMode"Simple", "Animated""Simple"Reorder animation style
Appearance Props
Prop Type Default Description showBorderbooleanfalseShow border around list showAlternatingRowBackgrounds"None", "ContentOnly", "All""None"Zebra striping showFoldoutHeaderbooleanfalseCollapsible header headerTitlestringHeader text showAddRemoveFooterbooleanfalseAdd/remove buttons
Why Imperative API?
ListView uses callbacks instead of React children because it handles virtualization internally. Unity's ListView recycles elements as you scroll, so:
makeItem creates visual elements only when needed
bindItem updates recycled elements with new data
This is much more efficient than React diffing for large lists
Selection Example
Copy function SelectableList ({ items , onSelect }) {
const [ selectedIndex , setSelectedIndex ] = useState ( - 1 )
return (
< ListView
itemsSource = {items}
fixedItemHeight = { 50 }
selectionType = "Single"
selectedIndex = {selectedIndex}
onSelectionChange = {( indices ) => {
setSelectedIndex (indices[ 0 ] ?? - 1 )
onSelect (items[indices[ 0 ]])
}}
makeItem = {() => {
const label = new CS .UnityEngine.UIElements. Label ()
label.style.paddingLeft = 10
label.style.paddingTop = 15
return label
}}
bindItem = {( element , index ) => {
element.text = items[index].name
}}
style = {{ height: 400 }}
/>
)
}
Complex Items
Copy function ContactList ({ contacts }) {
return (
< ListView
itemsSource = {contacts}
fixedItemHeight = { 60 }
makeItem = {() => {
const container = new CS .UnityEngine.UIElements. VisualElement ()
container.style.flexDirection = CS .UnityEngine.UIElements.FlexDirection.Row
container.style.alignItems = CS .UnityEngine.UIElements.Align.Center
container.style.paddingLeft = 10
container.style.paddingRight = 10
const avatar = new CS .UnityEngine.UIElements. VisualElement ()
avatar.name = "avatar"
avatar.style.width = 40
avatar.style.height = 40
avatar.style.borderRadius = 20
avatar.style.backgroundColor = new CS .UnityEngine. Color ( 0.3 , 0.3 , 0.3 , 1 )
container. Add (avatar)
const info = new CS .UnityEngine.UIElements. VisualElement ()
info.style.marginLeft = 10
const name = new CS .UnityEngine.UIElements. Label ()
name.name = "name"
name.style.fontSize = 16
info. Add (name)
const email = new CS .UnityEngine.UIElements. Label ()
email.name = "email"
email.style.fontSize = 12
email.style.color = new CS .UnityEngine. Color ( 0.6 , 0.6 , 0.6 , 1 )
info. Add (email)
container. Add (info)
return container
}}
bindItem = {( element , index ) => {
const contact = contacts[index]
element. Q ( "name" ).text = contact.name
element. Q ( "email" ).text = contact.email
}}
style = {{ height: 500 }}
/>
)
}
Alternating Rows
Copy < ListView
itemsSource = {items}
fixedItemHeight = { 40 }
showAlternatingRowBackgrounds = "ContentOnly"
makeItem = {() => new CS .UnityEngine.UIElements. Label ()}
bindItem = {( element , index ) => {
element.text = items[index]
}}
/>
Multiple Selection
Copy function MultiSelectList ({ items }) {
const [ selected , setSelected ] = useState ([])
return (
< View >
< Label text = { `Selected: ${ selected . length } items` } />
< ListView
itemsSource = {items}
fixedItemHeight = { 40 }
selectionType = "Multiple"
selectedIndices = {selected}
onSelectionChange = {setSelected}
makeItem = {() => new CS .UnityEngine.UIElements. Label ()}
bindItem = {( element , index ) => {
element.text = items[index]
}}
style = {{ height: 300 }}
/>
</ View >
)
}
Use fixedItemHeight when possible - it's faster than dynamic height
Keep makeItem simple - complex element creation slows initialization
Cache element queries - use element.Q() sparingly in bindItem
Minimize allocations - reuse objects in bindItem
Copy // Good: Simple, fast
makeItem = {() => new CS.UnityEngine.UIElements.Label()}
// Avoid: Complex hierarchy in makeItem
makeItem = {() => {
// Building complex trees here is slow
// Consider USS classes instead
}}