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