Gothic Framework G symbol

Topics

Topics let multiple WASM components on the same page share reactive state. Think of them as Gothic's version of React's useContext. One component publishes a value and every component subscribed to that field reacts automatically.

A topic is just a struct declared in src/topics/. Each field becomes an *ObservableField[T] with .Get(), .Set(), and .Peek() methods. Pass any field to Observe as a dependency and your callback re-runs only when that field changes.

Important: You must dot-import core/wasm (import . "github.com/gothicframework/core/wasm") for use these helpers, otherwise you will get a tinygo compile error.

Note: there is no mount step. Once a topic is declared in src/topics/, the always-loaded Gothic core registers it automatically. Any component can then grab it with its generated accessor and start reading or publishing.

1. Declare the Topic

Create a file in src/topics/ and call CreateTopic. Gothic generates an accessor function named after SubscriberFnName (here AppTopic()) that every component uses to reach the same shared instance.

// src/topics/app_topic.go
package gothicwasm

import . "github.com/gothicframework/core/wasm"

type App struct {
	Count int
	Theme string
	Label string
}

var _ = CreateTopic(App{}, TopicConfig{
	Name:             "app",
	Compression:      BROTLI,
	SubscriberFnName: "AppTopic",
})

2. Publish from Any Component

Any component can grab the topic with AppTopic() and call .Set() on a field to publish an update. Use .Peek() when you need the current value without subscribing.

// writer component, publish a single field
ClientSideState: func() {
	topic := AppTopic()

	CreateWasmFunc("increment", func() {
		topic.Count.Set(topic.Count.Peek() + 1)
	})

	CreateWasmFunc("toggleTheme", func() {
		if topic.Theme.Peek() == "light" {
			topic.Theme.Set("dark")
		} else {
			topic.Theme.Set("light")
		}
	})
}

3. Subscribe to Reactive Fields

In another component, call AppTopic() to get the same shared instance. Pass a field to Observe as a dependency and the callback re-runs whenever that field changes.

Note: use .Get() inside an Observe callback to subscribe to the field. Use .Peek() to read the value without creating a subscription.

// reader component, reacts to remote changes
ClientSideState: func() {
	topic := AppTopic()

	// re-runs only when Count changes, not Theme or Label
	Observe(func() {
		SetText("count-display", strconv.Itoa(topic.Count.Get()))
	}, topic.Count)

	// react to Theme, toggle a class on the body
	Observe(func() {
		if topic.Theme.Get() == "dark" {
			AddClass("root", "dark")
		} else {
			RemoveClass("root", "dark")
		}
	}, topic.Theme)
}

Liked topics? Next, learn how to share code across WASM pages and render templ components client-side!