ListView

ListView renders large lists efficiently using virtualization. Only visible items are rendered. Maps to UI Toolkit's ListView.

Import

import { ListView } from "onejs-react"

Basic Usage

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

PropTypeDescription
itemsSourceunknown[]Array of items to display
makeItem() => VisualElementFactory function to create elements
bindItem(element, index) => voidFunction to populate element with data

Optional Props

PropTypeDefaultDescription
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

PropTypeDefaultDescription
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

PropTypeDefaultDescription
reorderablebooleanfalseEnable drag reordering
reorderMode"Simple", "Animated""Simple"Reorder animation style

Appearance Props

PropTypeDefaultDescription
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:

  1. makeItem creates visual elements only when needed
  2. bindItem updates recycled elements with new data
  3. This is much more efficient than React diffing for large lists

Selection Example

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

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

<ListView
    itemsSource={items}
    fixedItemHeight={40}
    showAlternatingRowBackgrounds="ContentOnly"
    makeItem={() => new CS.UnityEngine.UIElements.Label()}
    bindItem={(element, index) => {
        element.text = items[index]
    }}
/>

Multiple Selection

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

Performance Tips

  1. Use fixedItemHeight when possible - it's faster than dynamic height
  2. Keep makeItem simple - complex element creation slows initialization
  3. Cache element queries - use element.Q() sparingly in bindItem
  4. Minimize allocations - reuse objects in bindItem
// 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
}}