Refs & Direct Access#

Use React refs to access the underlying UI Toolkit VisualElement directly. This is useful for imperative operations, measurements, or accessing features not exposed through props.

Basic Usage#

import { useRef, useEffect } from "react"
import { View, Label } from "onejs-react"

function FocusableInput() {
    const inputRef = useRef(null)

    useEffect(() => {
        // Focus the element on mount
        inputRef.current?.Focus()
    }, [])

    return <TextField ref={inputRef} placeholder="Auto-focused" />
}

VisualElement API#

When you access ref.current, you get a proxy to the C# VisualElement. Available properties and methods:

Properties#

PropertyTypeDescription
__csHandlenumberInternal C# object handle
__csTypestringC# type name
namestringElement name
visiblebooleanVisibility state
enabledSelfbooleanWhether element is enabled
enabledInHierarchybooleanEnabled state including parents
focusablebooleanWhether element can receive focus
styleobjectInline style access
childCountnumberNumber of children
parentVisualElementParent element

Focus Methods#

const ref = useRef(null)

// Focus the element
ref.current?.Focus()

// Remove focus
ref.current?.Blur()

Class List Methods#

const ref = useRef(null)

// Add USS class
ref.current?.AddToClassList("highlighted")

// Remove USS class
ref.current?.RemoveFromClassList("highlighted")

// Check if class exists
const hasClass = ref.current?.ClassListContains("highlighted")

// Clear all classes
ref.current?.ClearClassList()

Hierarchy Methods#

const containerRef = useRef(null)

// Add a child element
const label = new CS.UnityEngine.UIElements.Label()
containerRef.current?.Add(label)

// Insert at index
containerRef.current?.Insert(0, label)

// Remove a child
containerRef.current?.Remove(label)

// Remove by index
containerRef.current?.RemoveAt(0)

// Clear all children
containerRef.current?.Clear()

// Find child index
const index = containerRef.current?.IndexOf(label)

Layout#

// Force repaint
ref.current?.MarkDirtyRepaint()

Typed Refs#

Components export typed element interfaces for better IntelliSense:

import type { TextFieldElement, ButtonElement, ScrollViewElement } from "onejs-react"

function Form() {
    const textFieldRef = useRef<TextFieldElement>(null)
    const buttonRef = useRef<ButtonElement>(null)

    const handleSubmit = () => {
        const value = textFieldRef.current?.value
        console.log("Submitted:", value)
    }

    return (
        <View>
            <TextField ref={textFieldRef} placeholder="Enter text" />
            <Button ref={buttonRef} text="Submit" onClick={handleSubmit} />
        </View>
    )
}

Available Element Types#

TypeSpecific Properties
VisualElementBase type for all elements
TextElementtext: string
LabelElementtext: string
ButtonElementtext: string
TextFieldElementvalue, text, isReadOnly, maxLength, SelectAll()
ToggleElementvalue: boolean, text: string
SliderElementvalue, lowValue, highValue
ScrollViewElementscrollOffset, ScrollTo(child)

TextField Methods#

import type { TextFieldElement } from "onejs-react"

function SearchInput() {
    const ref = useRef<TextFieldElement>(null)

    return (
        <View>
            <TextField
                ref={ref}
                placeholder="Search..."
            />
            <Button
                text="Clear"
                onClick={() => {
                    if (ref.current) {
                        ref.current.value = ""
                    }
                }}
            />
            <Button
                text="Select All"
                onClick={() => ref.current?.SelectAll()}
            />
        </View>
    )
}

ScrollView Methods#

import type { ScrollViewElement, VisualElement } from "onejs-react"

function AutoScroll() {
    const scrollRef = useRef<ScrollViewElement>(null)
    const lastItemRef = useRef<VisualElement>(null)

    const scrollToBottom = () => {
        if (scrollRef.current && lastItemRef.current) {
            scrollRef.current.ScrollTo(lastItemRef.current)
        }
    }

    return (
        <View>
            <ScrollView ref={scrollRef} style={{ height: 300 }}>
                {items.map((item, i) => (
                    <View
                        key={i}
                        ref={i === items.length - 1 ? lastItemRef : null}
                    >
                        <Label text={item} />
                    </View>
                ))}
            </ScrollView>
            <Button text="Scroll to Bottom" onClick={scrollToBottom} />
        </View>
    )
}

Measuring Elements#

Access resolved layout values:

function MeasuredElement() {
    const ref = useRef(null)
    const [size, setSize] = useState({ width: 0, height: 0 })

    return (
        <View
            ref={ref}
            onGeometryChanged={(e) => {
                setSize({
                    width: e.newRect.width,
                    height: e.newRect.height,
                })
            }}
        >
            <Label text={`Size: ${size.width}x${size.height}`} />
        </View>
    )
}

Direct Style Manipulation#

While inline styles are preferred, you can manipulate styles directly:

function AnimatedElement() {
    const ref = useRef(null)

    const animate = () => {
        if (ref.current) {
            ref.current.style.backgroundColor = new CS.UnityEngine.Color(1, 0, 0, 1)
        }
    }

    return (
        <View ref={ref} style={{ width: 100, height: 100, backgroundColor: "#333" }}>
            <Button text="Turn Red" onClick={animate} />
        </View>
    )
}

Callback Refs#

For more control, use callback refs:

function CallbackRefExample() {
    const handleRef = (element) => {
        if (element) {
            console.log("Element mounted:", element.__csType)
            element.AddToClassList("initialized")
        }
    }

    return <View ref={handleRef}>Content</View>
}

Best Practices#

  1. Prefer props over refs - Use refs only when declarative props aren't sufficient
  2. Check for null - Always check ref.current before accessing
  3. Use typed refs - Import element types for better autocomplete
  4. Avoid frequent direct manipulation - Let React handle updates when possible