@@ -267,7 +267,7 @@ var MyComponent = app.DefineComponent("MyComponent", func(props MyProps) any {
267267
268268- ** State Management** : app.UseLocal creates local component atoms (covered in State Management with Atoms)
269269- ** Component Lifecycle** : app.UseEffect, app.UseRef, app.UseVDomRef (covered in Component Lifecycle Hooks)
270- - ** Async Operations** : app.UseGoRoutine manages goroutine lifecycle (covered in Async Operations and Goroutines)
270+ - ** Async Operations** : app.UseGoRoutine, app.UseTicker, app.UseAfter manage goroutine and timer lifecycle (covered in Async Operations and Goroutines)
271271- ** Utility** : app.UseSetAppTitle, app.UseId, app.UseRenderTs, app.UseResync
272272
273273## State Management with Atoms
@@ -925,31 +925,107 @@ var App = app.DefineComponent("App", func(_ struct{}) any {
925925
926926When working with goroutines, timers, or other async operations in Tsunami, follow these patterns to safely update state and manage cleanup:
927927
928- ### Goroutine Management
928+ ### Timer Hooks
929929
930- For async operations like timers, background tasks, or data polling, use app.UseGoRoutine to safely manage goroutine lifecycle:
930+ For common timing operations, Tsunami provides simplified hooks that handle cleanup automatically:
931+
932+ #### UseTicker for Recurring Operations
933+
934+ Use ` app.UseTicker ` for operations that need to run at regular intervals:
931935
932936``` go
933- var MyComponent = app.DefineComponent (" MyComponent" , func (_ struct {}) any {
934- seconds := app.UseLocal (0 )
937+ var ClockComponent = app.DefineComponent (" ClockComponent" , func (_ struct {}) any {
938+ currentTime := app.UseLocal (time.Now ().Format (" 15:04:05" ))
939+
940+ // Update every second - automatically cleaned up on unmount
941+ app.UseTicker (time.Second , func () {
942+ currentTime.Set (time.Now ().Format (" 15:04:05" ))
943+ app.SendAsyncInitiation ()
944+ }, []any{})
945+
946+ return vdom.H (" div" , map [string ]any{
947+ " className" : " text-2xl font-mono" ,
948+ }, " Current time: " , currentTime.Get ())
949+ })
950+ ```
951+
952+ #### UseAfter for Delayed Operations
953+
954+ Use ` app.UseAfter ` for one-time delayed operations:
955+
956+ ``` go
957+ type ToastComponentProps struct {
958+ Message string
959+ Duration time.Duration
960+ }
961+
962+ var ToastComponent = app.DefineComponent (" ToastComponent" , func (props ToastComponentProps ) any {
963+ visible := app.UseLocal (true )
964+
965+ // Auto-hide after specified duration - cancelled if component unmounts
966+ app.UseAfter (props.Duration , func () {
967+ visible.Set (false )
968+ app.SendAsyncInitiation ()
969+ }, []any{props.Duration })
970+
971+ if !visible.Get () {
972+ return nil
973+ }
974+
975+ return vdom.H (" div" , map [string ]any{
976+ " className" : " bg-blue-500 text-white p-4 rounded" ,
977+ }, props.Message )
978+ })
979+ ```
980+
981+ ** Benefits of Timer Hooks:**
982+
983+ - ** Automatic cleanup** : Timers are stopped when component unmounts or dependencies change
984+ - ** No goroutine leaks** : Built on top of ` UseGoRoutine ` with proper context cancellation
985+ - ** Simpler API** : No need to manually manage ticker channels or timer cleanup
986+ - ** Dependency tracking** : Change dependencies to restart timers with new intervals
987+
988+ ### Complex Async Operations with UseGoRoutine
935989
936- timerFn := func (ctx context.Context ) {
990+ For more complex async operations like data polling, background processing, or custom timing logic, use ` app.UseGoRoutine ` directly:
991+
992+ ``` go
993+ var DataPollerComponent = app.DefineComponent (" DataPollerComponent" , func (_ struct {}) any {
994+ data := app.UseLocal ([]APIResult {})
995+ status := app.UseLocal (" idle" )
996+
997+ pollDataFn := func (ctx context.Context ) {
937998 for {
938999 select {
9391000 case <- ctx.Done ():
9401001 return
941- case <- time.After (time.Second ):
942- // Update state from goroutine
943- seconds.SetFn (func (s int ) int { return s + 1 })
944- app.SendAsyncInitiation () // Trigger UI update
1002+ case <- time.After (30 * time.Second ):
1003+ status.Set (" fetching" )
1004+ app.SendAsyncInitiation ()
1005+
1006+ // Complex async operation: fetch, process, validate
1007+ newData , err := fetchAndProcessData ()
1008+ if err != nil {
1009+ status.Set (" error" )
1010+ } else {
1011+ data.SetFn (func (current []APIResult) []APIResult {
1012+ // Merge new data with existing, handle deduplication
1013+ return mergeResults (current, newData)
1014+ })
1015+ status.Set (" success" )
1016+ }
1017+ app.SendAsyncInitiation ()
9451018 }
9461019 }
9471020 }
9481021
949- // Start timer on mount, cleanup on unmount
950- app.UseGoRoutine (timerFn , []any{})
1022+ // Start polling on mount, cleanup on unmount
1023+ app.UseGoRoutine (pollDataFn , []any{})
9511024
952- return vdom.H (" div" , nil , " Seconds: " , seconds.Get ())
1025+ return vdom.H (" div" , nil ,
1026+ vdom.H (" div" , nil , " Status: " , status.Get ()),
1027+ vdom.H (" div" , nil , " Data count: " , len (data.Get ())),
1028+ )
9531029})
9541030```
9551031
@@ -1282,6 +1358,8 @@ Key points:
12821358** Async Operation Guidelines**
12831359
12841360- Use app.UseGoRoutine instead of raw go statements for component-related async work
1361+ - Use app.UseTicker instead of manual time.Ticker management for recurring operations
1362+ - Use app.UseAfter instead of time.AfterFunc for delayed operations
12851363- Always respect ctx.Done() in app.UseGoRoutine functions to prevent goroutine leaks
1286- - Use app.UseEffect with cleanup functions for subscriptions, timers, and other lifecycle management
1364+ - All timer and goroutine cleanup is handled automatically on component unmount or dependency changes
12871365- Call app.SendAsyncInitiation after state updates to trigger re-rendering
0 commit comments