state

package
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 0 Imported by: 0

README

State Package

Location: /state

GoWebComponents/
├── dom/
├── hooks/
├── state/            ← YOU ARE HERE
│   ├── doc.go
│   └── state.go
├── render/
├── router/
├── fetch/
├── internal/
├── examples/
└── ...

Overview

The state package provides global state management through atoms - named pieces of state that can be shared across components. Unlike hooks.UseState which is local to a component, atoms enable multiple components to subscribe to and update the same state.

Core API

UseAtom[T](key string, initialValue T) (func() T, func(T))

Creates or connects to a global state atom identified by a unique key.

// Component A - Controller
func CounterController(props dom.Attrs) *dom.Element {
    count, setCount := state.UseAtom("global-counter", 0)

    handleIncrement := hooks.GoUseFunc(func(e dom.GoEvent) {
        setCount(count() + 1)
    })

    return dom.Button(dom.Attrs{
        "onclick": handleIncrement,
    }, dom.Text("Increment"))
}

// Component B - Display
func CounterDisplay(props dom.Attrs) *dom.Element {
    count, _ := state.UseAtom("global-counter", 0)

    return dom.P(nil,
        dom.Text(fmt.Sprintf("Count: %d", count())))
}

Key Features:

  • Shared State: Multiple components can subscribe to the same atom
  • Type-Safe: Generic type parameter ensures type safety
  • Automatic Re-renders: All subscribed components re-render when atom updates
  • Initial Value: If atom doesn't exist, creates it with initialValue

Use Cases

1. Application Theme
// In theme toggle component
theme, setTheme := state.UseAtom("app-theme", "light")

toggleTheme := hooks.GoUseFunc(func(e dom.GoEvent) {
    if theme() == "light" {
        setTheme("dark")
    } else {
        setTheme("light")
    }
})

// In any other component
theme, _ := state.UseAtom("app-theme", "light")
className := theme() + "-mode"
2. User Session
type User struct {
    ID       int
    Username string
    Email    string
}

// In login component
user, setUser := state.UseAtom("current-user", User{})

// In navbar
user, _ := state.UseAtom("current-user", User{})
return dom.Span(nil, dom.Text(user().Username))

// In profile page
user, _ := state.UseAtom("current-user", User{})
// Access user().Email, user().ID, etc.
3. Shopping Cart
type CartItem struct {
    ProductID int
    Quantity  int
    Price     float64
}

// Add to cart button
cart, setCart := state.UseAtom("shopping-cart", []CartItem{})

addToCart := hooks.GoUseFunc(func(e dom.GoEvent) {
    currentCart := cart()
    newCart := append(currentCart, CartItem{
        ProductID: productID,
        Quantity: 1,
        Price: product.Price,
    })
    setCart(newCart)
})

// Cart display component
cart, _ := state.UseAtom("shopping-cart", []CartItem{})
total := calculateTotal(cart())
4. Modal/Dialog State
// In any component
isOpen, setIsOpen := state.UseAtom("modal-open", false)

openModal := hooks.GoUseFunc(func(e dom.GoEvent) {
    setIsOpen(true)
})

// In modal component
isOpen, setIsOpen := state.UseAtom("modal-open", false)

if !isOpen() {
    return nil // Don't render modal
}

return dom.Dialog(/* ... modal content ... */)

Atom Patterns

Read-Only Access

Components that only need to read an atom can ignore the setter:

theme, _ := state.UseAtom("app-theme", "light")
// Only uses theme(), never updates it
Write-Only Access

Components that only update can ignore the getter (less common):

_, setTheme := state.UseAtom("app-theme", "light")
// Only calls setTheme(), doesn't read current value
Derived State

Compute values based on atom state:

cart, _ := state.UseAtom("shopping-cart", []CartItem{})

total := hooks.UseMemo(func() interface{} {
    sum := 0.0
    for _, item := range cart() {
        sum += item.Price * float64(item.Quantity)
    }
    return sum
}, []interface{}{cart()}).(float64)

Best Practices

1. Use Descriptive Keys
// Good
state.UseAtom("user-preferences", prefs)
state.UseAtom("shopping-cart-items", items)

// Avoid
state.UseAtom("data", something)
state.UseAtom("x", value)
2. Initialize with Proper Types
// Struct
state.UseAtom("user", User{})

// Slice
state.UseAtom("items", []Item{})

// Map
state.UseAtom("cache", map[string]interface{}{})

// Primitive
state.UseAtom("count", 0)
state.UseAtom("enabled", false)
state.UseAtom("message", "")
3. Consider Component State First

Use atoms for:

  • ✅ State needed by multiple distant components
  • ✅ Global application state (theme, user, cart)
  • ✅ State that persists across route changes

Use hooks.UseState for:

  • ✅ Form input values
  • ✅ Component-specific UI state (expanded, selected)
  • ✅ Temporary local state

When to Use Atoms vs. Props

Scenario Use Atoms Use Props
Parent-child communication
Sibling communication
Distant components (no parent)
Global app state
Component configuration
One-time values
Frequently changing shared state

Implementation Details

  • Atoms are stored in a global registry by key
  • Each atom maintains a list of subscribed components
  • When an atom updates, all subscribed components are marked for re-render
  • Type safety enforced through generics at compile time

Examples

See atom usage in:

Documentation

See doc.go for the official Go package documentation.

Documentation

Overview

Package state provides global state management with fine-grained reactivity for GoWebComponents.

This package implements atom-based state management inspired by SolidJS and Jotai, allowing components to subscribe to global state that persists across the entire application and automatically triggers re-renders when values change.

Basic usage:

import "github.com/monstercameron/GoWebComponents/state"

// In any component
func UserProfile() ui.Node {
    username := state.UseAtom("currentUser", "Guest")
    login := ui.UseEvent(func() {
        username.Set("John Doe")
    })

    return html.Div(html.Props{},
        html.H1(html.Props{}, html.Text(fmt.Sprintf("Welcome, %s", username.Get()))),
        html.Button(html.Props{OnClick: login}, html.Text("Login")),
    )
}

// In a different component - shares the same state!
func NavBar() ui.Node {
    username := state.UseAtom("currentUser", "Guest")
    return html.Nav(html.Props{}, html.Span(html.Props{}, html.Text(username.Get())))
}

Key features:

  • Global state accessible from any component by ID
  • Automatic subscription and re-rendering when state changes
  • Type-safe with Go generics
  • Thread-safe for concurrent access
  • Fine-grained reactivity - only subscribed components re-render

Atoms vs Component State:

  • Use state.UseAtom for data that needs to be shared across components
  • Use ui.UseState for local component state
  • Atoms persist across component unmounts
  • Atoms trigger updates in all subscribed components

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Atom

type Atom[T any] struct {
	// contains filtered or unexported fields
}

func UseAtom

func UseAtom[T any](id string, initialValue T) Atom[T]

UseAtom provides SolidJS-style fine-grained reactivity with global atoms. Atoms are accessible from anywhere in the component tree by ID and automatically trigger re-renders in all subscribed components when updated.

The first component to call UseAtom with a specific ID initializes the atom with the provided initial value. Subsequent calls from other components will use the existing value and subscribe to updates.

Type parameter T can be any Go type. The hook uses generic type parameters for type safety.

Returns:

  • A getter function that returns the current atom value
  • A setter function that updates the atom and triggers re-renders in all subscribers

Example - Theme Management:

// In theme switcher component
func ThemeSwitcher(props dom.Attrs) *fiber.Element {
    theme, setTheme := state.UseAtom("appTheme", "light")

    toggle := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        if theme() == "light" {
            setTheme("dark")
        } else {
            setTheme("light")
        }
        return nil
    })

    return dom.Button(map[string]interface{}{"onclick": toggle},
        fmt.Sprintf("Switch to %s mode", theme()))
}

// In header component - automatically updates when theme changes
func Header(props dom.Attrs) *fiber.Element {
    theme, _ := state.UseAtom("appTheme", "light")

    bgColor := "white"
    if theme() == "dark" {
        bgColor = "#333"
    }

    return dom.Header(map[string]interface{}{
        "style": map[string]string{"background-color": bgColor},
    }, dom.H1(nil, "My App"))
}

Example - User Authentication:

type User struct {
    ID       int
    Username string
    Email    string
}

func LoginButton(props dom.Attrs) *fiber.Element {
    user, setUser := state.UseAtom("currentUser", User{})

    login := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        setUser(User{ID: 1, Username: "johndoe", Email: "john@example.com"})
        return nil
    })

    if user().ID == 0 {
        return dom.Button(map[string]interface{}{"onclick": login}, "Login")
    }
    return dom.Span(nil, fmt.Sprintf("Welcome, %s", user().Username))
}

Thread Safety: UseAtom is thread-safe and can be safely called from multiple goroutines. The internal atom registry uses mutex-based synchronization.

Cleanup: When a component unmounts, it is automatically unsubscribed from all atoms to prevent memory leaks and unnecessary updates.

Best Practices:

  • Use descriptive atom IDs (e.g., "currentUser", "appTheme", "shoppingCart")
  • Initialize atoms with appropriate default values

Consider using structured types (structs) for complex state

  • Avoid storing large amounts of data in atoms (use for coordination, not caching)
Example
package main

import (
	"fmt"

	"github.com/monstercameron/GoWebComponents/state"
)

func main() {
	// Note: UseAtom must be used inside a component

	// Access or create a global atom named "user" with default value "Guest"
	user := state.UseAtom("user", "Guest")

	// Get current value
	fmt.Printf("Current user: %s\n", user.Get())

	// Update value - this will trigger updates in all components using this atom
	user.Set("Alice")
}

func (Atom[T]) Get

func (a Atom[T]) Get() T

func (Atom[T]) Set

func (a Atom[T]) Set(value T)

func (Atom[T]) Update

func (a Atom[T]) Update(fn func(T) T)

type Element

type Element = runtime.Element

Type alias for Element

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL