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
}}