Skip to content

Commit ee30814

Browse files
committed
WithKey takes any. fix tailwind stuff in system.md. fix deps in UseEffect to match react
1 parent 0483923 commit ee30814

3 files changed

Lines changed: 38 additions & 20 deletions

File tree

tsunami/comp/hooks.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ func (vc *VDomContextImpl) UseEffect(ctx context.Context, fn func() func(), deps
162162
vc.Root.AddEffectWork(vc.getCompWaveId(), hookVal.Idx)
163163
return
164164
}
165+
// If deps is nil, always run (like React with no dependency array)
166+
if deps == nil {
167+
hookVal.Fn = fn
168+
hookVal.Deps = deps
169+
vc.Root.AddEffectWork(vc.getCompWaveId(), hookVal.Idx)
170+
return
171+
}
172+
165173
if depsEqual(hookVal.Deps, deps) {
166174
return
167175
}

tsunami/prompts/system.md

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ vdom.H("ul", nil,
208208
vdom.ForEach(items, func(item string, idx int) any {
209209
return vdom.H("li", map[string]any{
210210
"key": idx,
211-
"className": "list-item",
211+
"className": "py-2 px-3 border-b border-gray-100",
212212
}, item)
213213
}),
214214
)
@@ -326,7 +326,7 @@ This makes Tsunami applications naturally suitable for integration with external
326326

327327
## Style Handling
328328

329-
Tsunami applications use Tailwind v4 CSS by default for styling (className prop). You can also define inline styles using a map[string]any in the props:
329+
Tsunami applications use Tailwind v4 CSS by default for styling (className prop) and you should favor styling with Tailwind whenever possible. However, you may also define inline styles using a map[string]any in the props:
330330

331331
```go
332332
vdom.H("div", map[string]any{
@@ -359,6 +359,14 @@ Properties use camelCase (must match React) and values can be:
359359

360360
The style map in props mirrors React's style object pattern, making it familiar to React developers while maintaining type safety in Go.
361361

362+
### External Styles and Stylesheets
363+
364+
Quick styles can be added using a vdom.H("style", nil, "...") tag. You may also place CSS files in the `static` directory, and serve them directly with:
365+
366+
```go
367+
vdom.H("link", map[string]any{"rel": "stylesheet", "src": "/static/mystyles.css"})
368+
```
369+
362370
## Component Definition Pattern
363371

364372
Create typed, reusable components using the client:
@@ -376,21 +384,17 @@ var TodoItem = app.DefineComponent("TodoItem",
376384
func(ctx context.Context, props TodoItemProps) any {
377385
return vdom.H("div", map[string]any{
378386
"className": vdom.Classes(
379-
"todo-item",
380-
vdom.If(props.IsActive, "active"),
387+
"p-3 border-b border-gray-200 cursor-pointer transition-opacity",
388+
vdom.If(props.IsActive, "opacity-100 bg-blue-50", "opacity-70 hover:bg-gray-50"),
381389
),
382390
"onClick": props.OnToggle,
383-
"style": map[string]any{
384-
"cursor": "pointer",
385-
"opacity": vdom.If(props.IsActive, 1.0, 0.7),
386-
},
387391
}, props.Todo.Text)
388392
},
389393
)
390394

391395
// Usage in parent component:
392396
vdom.H("div", map[string]any{
393-
"className": "todo-list",
397+
"className": "bg-white rounded-lg shadow-sm border",
394398
},
395399
TodoItem(TodoItemProps{
396400
Todo: todo,
@@ -403,7 +407,7 @@ vdom.H("div", map[string]any{
403407
TodoItem(TodoItemProps{
404408
Todo: todo,
405409
OnToggle: handleToggle,
406-
}).WithKey(fmt.Sprint(idx))
410+
}).WithKey(idx)
407411
```
408412

409413
Components in Tsunami:
@@ -417,10 +421,11 @@ Components in Tsunami:
417421

418422
Special Handling for Component "key" prop:
419423

420-
- Use the WithKey(key string) chaining func to set a key on a component
424+
- Use the `WithKey(key any)` chaining func to set a key on a component
421425
- Keys must be added for components rendered in lists (just like in React)
422426
- Keys should be unique among siblings and stable across renders
423427
- Keys are handled at the framework level and should not be declared in component props
428+
- `WithKey` accepts any type and automatically converts it to a string using fmt.Sprint
424429

425430
This pattern matches React's functional components while maintaining Go's type safety and explicit props definition.
426431

@@ -466,7 +471,7 @@ keyHandler := &vdom.VDomFunc{
466471
}
467472

468473
vdom.H("input", map[string]any{
469-
"className": "special-input",
474+
"className": "px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500",
470475
"onKeyDown": keyHandler,
471476
})
472477

@@ -555,8 +560,10 @@ func MyComponent(ctx context.Context, props MyProps) any {
555560
},
556561
}),
557562
vdom.H("ul", nil,
558-
vdom.ForEach(items, func(item string, _ int) any {
559-
return vdom.H("li", nil, item)
563+
vdom.ForEach(items, func(item string, idx int) any {
564+
return vdom.H("li", map[string]any{
565+
"key": idx,
566+
}, item)
560567
}),
561568
),
562569
)
@@ -584,7 +591,7 @@ The system provides three main types of hooks:
584591
})
585592
```
586593

587-
2. vdom.UseRef - For values that persist between renders without triggering updates:
594+
2. vdom.UseRef - For values that persist between renders without triggering updates (like React.useRef):
588595

589596
- Holds mutable values that survive re-renders
590597
- Changes don't cause re-renders
@@ -593,6 +600,7 @@ The system provides three main types of hooks:
593600
- Storing timers/channels
594601
- Tracking subscriptions
595602
- Holding complex state structures
603+
- Unlike React, this ref CANNOT be set as the ref prop on an element
596604

597605
```go
598606
timerRef := vdom.UseRef(ctx, &TimerState{
@@ -607,6 +615,7 @@ The system provides three main types of hooks:
607615
- Managing focus
608616
- Measuring elements
609617
- Direct DOM manipulation when needed
618+
- These ref objects SHOULD be set as ref prop on elements.
610619
```go
611620
inputRef := vdom.UseVDomRef(ctx)
612621
vdom.H("input", map[string]any{
@@ -696,8 +705,9 @@ var TodoApp = app.DefineComponent("TodoApp",
696705
}, []any{})
697706

698707
return vdom.H("div", nil,
699-
vdom.ForEach(globalTodos, func(todo Todo, _ int) any {
700-
return TodoItem(TodoItemProps{Todo: todo})
708+
vdom.ForEach(globalTodos, func(todo Todo, idx int) any {
709+
// Use WithKey() for adding keys to components
710+
return TodoItem(TodoItemProps{Todo: todo}).WithKey(idx)
701711
}),
702712
)
703713
},
@@ -923,7 +933,7 @@ var App = app.DefineComponent("App",
923933
Todo: todo,
924934
OnToggle: func() { toggleTodo(todo.Id) },
925935
OnDelete: func() { deleteTodo(todo.Id) },
926-
}).WithKey(strconv.Itoa(todo.Id))
936+
}).WithKey(todo.Id)
927937
})),
928938
)
929939
},

tsunami/vdom/vdom.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ func (e *VDomElem) Key() string {
3030
// WithKey sets the key property of the VDomElem and returns the element.
3131
// This is particularly useful for defined components since their prop types won't include keys.
3232
// Returns nil if the element is nil, otherwise returns the same element for chaining.
33-
func (e *VDomElem) WithKey(key string) *VDomElem {
33+
func (e *VDomElem) WithKey(key any) *VDomElem {
3434
if e == nil {
3535
return nil
3636
}
3737
if e.Props == nil {
3838
e.Props = make(map[string]any)
3939
}
40-
e.Props[KeyPropKey] = key
40+
e.Props[KeyPropKey] = fmt.Sprint(key)
4141
return e
4242
}
4343

0 commit comments

Comments
 (0)