-
Notifications
You must be signed in to change notification settings - Fork 4
State & Hooks
Hooks allow a component to remember information between renders.
Without hooks, every time RetUI redraws the screen, your component would start from scratch and lose all of its state.
Most applications only need four hooks:
UseStateUseStateKeyedUseEffectUseContext
RetUI also provides two keyboard hooks for building custom interactive components.
A hook is simply a function that gives your component extra capabilities.
For example:
- Remember a value
- React to changes
- Share data with other components
- Handle keyboard input
Hooks can only be called inside a component.
UseState stores a value that can change over time.
count, setCount := retui.UseState(0)count contains the current value.
setCount updates the value.
setCount(count + 1)Whenever you call setCount(), RetUI automatically redraws the component.
func Counter() retui.Element {
count, setCount := retui.UseState(0)
return components.Button().
Label(fmt.Sprintf("Count: %d", count)).
OnPress(func() {
setCount(count + 1)
})
}Every button press increases the counter.
A component can have more than one state.
name, setName := retui.UseState("")
age, setAge := retui.UseState(0)
active, setActive := retui.UseState(false)Each state is independent.
UseState stores values by the order hooks are called.
Sometimes that isn't enough.
Imagine a tree view where items appear and disappear.
▶ Documents
▶ Downloads
▶ Pictures
Each node needs its own state.
Instead of using call order, UseStateKeyed stores values using a unique key.
expanded, setExpanded :=
retui.UseStateKeyed("node-"+id, false)No matter how the tree changes, the correct state is always restored.
Use UseStateKeyed when:
- Tree views
- Dynamic lists
- Variable forms
- Any component created from changing data
UseEffect runs code when something changes.
retui.UseEffect(func() func() {
retui.Debug("Count changed")
return nil
}, []any{count})The first argument is the function to run.
The second argument is a list of dependencies.
The effect only runs when one of those values changes.
Sometimes you need to clean something up before the effect runs again.
retui.UseEffect(func() func() {
startWatcher()
return func() {
stopWatcher()
}
}, []any{path})Cleanup runs automatically before the effect executes again.
Good examples:
- Start a timer
- Save data
- Watch a file
- Log information
- Call an API
- Subscribe to events
Avoid using it for values that can simply be calculated during rendering.
Sometimes many components need the same information.
For example:
- Theme
- Language
- Current user
- Application settings
Passing these values through every component quickly becomes messy.
Context solves this problem.
Create a context once.
var ThemeContext =
retui.CreateContext("dark")The value "dark" is the default.
Provide a value for part of your application.
ThemeContext.Provide("light", func() retui.Element {
return Dashboard()
})Everything inside Dashboard now receives "light".
Inside any child component:
theme := retui.UseContext(ThemeContext)Now theme contains the current value.
No matter how deeply nested the component is.
Sometimes a component wants to handle keyboard input itself.
key, ok := retui.UseFocusedKeySimple(
retui.IsFocused("search")
)
if ok {
switch key.Code {
case retui.KeyEnter:
// Handle Enter
case retui.KeyEsc:
// Handle Escape
}
}This hook only delivers keys when the component has focus.
UseFocusedKey works like UseFocusedKeySimple but also supports focus capture.
This is useful for:
- Modal dialogs
- Popups
- Menus
- Command palettes
key, ok :=
retui.UseFocusedKey(
"search",
retui.IsFocused("search"),
)In most applications, UseFocusedKeySimple is enough.
Hooks are simple, but they follow one important rule.
Always call hooks in the same order.
✅ Good
count, setCount := retui.UseState(0)
theme := retui.UseContext(ThemeContext)
retui.UseEffect(...)❌ Don't call hooks inside an if
if loggedIn {
retui.UseState(0)
}❌ Don't call hooks inside a loop
for _, item := range items {
retui.UseState(false)
}Changing the order of hooks can cause state to be assigned to the wrong component.
| Hook | Purpose |
|---|---|
UseState |
Store a value that changes |
UseStateKeyed |
Store state for dynamic items |
UseEffect |
Run code when values change |
CreateContext |
Create shared state |
UseContext |
Read shared state |
UseFocusedKeySimple |
Handle keyboard input for a focused component |
UseFocusedKey |
Handle keyboard input with focus capture support |
Hooks make RetUI applications interactive.
They allow components to:
- Remember values
- React to changes
- Share information
- Handle keyboard input
For most applications, you'll mainly use:
UseStateUseEffectUseContext
Everything else builds on these core ideas.