Skip to content

Reactivity

A component function runs once when its window (or parent) mounts. Signals keep text and properties up to date without rebuilding the whole tree.

#Reactive state

Create a signal with ui.CreateSignal. Read it in text and properties: ui.Text("Count: ", count()), .Width(width()). Event handlers batch writes automatically.

func TextExample() *ui.Element {
	count, setCount := ui.CreateSignal(0)
	return ui.View().Children(
		ui.Text(
			"Count: ",
			count(),
		).FontSize(20),
		ui.Button().Child("Increment").OnClick(func() { setCount(count() + 1) }),
	)
}

initial := count() is a snapshot. Read the signal again later when the value should stay live. You can also pass a signal as a child: ui.Text("Count: ", count) keeps the prefix and updates only the number.

Custom components take ordinary int, string, and struct arguments. LineItem(product, quantity()) stays subscribed to quantity.

#Derived state

Use ui.CreateMemo for a value computed from other signals.

func DerivedExample() *ui.Element {
	count, setCount := ui.CreateSignal(0)
	doubled := ui.CreateMemo(func() int { return count() * 2 })
	return ui.View().Children(
		ui.Text("Doubled: ", doubled()),
		ui.Button().Child("Increment").OnClick(func() { setCount(count() + 1) }),
	)
}

#Effects and cleanup

Use ui.CreateEffect for work outside the tree, such as a subscription or a window title. It runs after mount and again when a value it read changes. ui.OnCleanup runs before the next execution and when the component goes away.

func EffectExample() *ui.Element {
	topic, setTopic := ui.CreateSignal("Inbox")
	ui.CreateEffect(func() {
		current := topic()
		log.Print("Subscribe: ", current)
		ui.OnCleanup(func() { log.Print("Unsubscribe: ", current) })
	})
	return ui.View().Children(
		ui.Text("Watching: ", topic()),
		ui.Button().Child("Switch topic").OnClick(func() {
			if topic() == "Inbox" {
				setTopic("Archive")
			} else {
				setTopic("Inbox")
			}
		}),
	)
}

Update text and properties directly. Do not rebuild UI inside an effect.

#Batched updates

Click handlers already batch writes. Wrap related writes elsewhere in ui.Batch so observers see one result. ui.Untrack reads a signal without subscribing. Read and write signals on the UI goroutine; use ui.Async or native.Dispatch to deliver background results.

func BatchedExample() *ui.Element {
	first, setFirst := ui.CreateSignal("Ada")
	last, setLast := ui.CreateSignal("Lovelace")
	ui.CreateEffect(func() { log.Print(first() + " " + last()) })
	applyProfile := func() {
		ui.Batch(func() {
			setFirst("Grace")
			setLast("Hopper")
		})
	}
	return ui.View().Children(
		ui.Text(first(), " ", last()),
		ui.Button().Child("Apply profile").OnClick(applyProfile),
	)
}