charmbracelet / charmbracelet/bubbles

Components do not satisfy tea.Model interface

Open
#190 6 comments 8 reactions 1 assignee Claimed by @meowgorithm View on GitHub
enhancement v2
Dominant language
Go
Stars
8.9k
Forks
457
Avg merge
1d 18h
Merged PRs (30d)
5

Description

Rationale: I want to write a function that works with any kind of component. I'm not seeing how to achieve this cleanly, since components actually do not satisfy the `github.com/charmbracelet/bubbletea.Model` interface. Please correct me if I'm misunderstanding something; I'm totally new to bubbletea!

---

**Problem 1.** Missing `Init` method. Affected components:

- cursor
- help
- list
- paginator
- spinner
- textarea
- textinput

Not affected: progress, stopwatch, timer, viewport.

```
var m tea.Model = textinput.New()
// cannot use textinput.New() (value of type textinput.Model)
// as tea.Model value in variable declaration:
// missing method Init
```

---

**Problem 2.** The `Update` method returns concrete component's `Model`, which makes it incompatible with `tea.Model`:

```
var m tea.Model = timer.New(1 * time.Second)
// cannot use timer.New(1 * time.Second) (value of type timer.Model)
// as tea.Model value in variable declaration: wrong type for method Update (
// have func(msg github.com/charmbracelet/bubbletea.Msg) (github.com/charmbracelet/bubbles/timer.Model, github.com/charmbracelet/bubbletea.Cmd),
// want func(github.com/charmbracelet/bubbletea.Msg) (github.com/charmbracelet/bubbletea.Model, github.com/charmbracelet/bubbletea.Cmd))
```

Backwards compatibility aside, consider using a type parameter in declaration of `tea.Model`:

```
type MyModel[T any] interface {
Update() MyModel[T]
// skipped for brevity:
// Init() tea.Cmd
// Update(tea.Msg) (MyModel[T], tea.Cmd)
// View() string
}

type timerModel struct{}
type viewportModel struct{}

func (m timerModel) Update() MyModel[timerModel] { return m }
func (m viewportModel) Update() MyModel[viewportModel] { return m }

func updateModel[T any](m *MyModel[T]) {
// e.g.: updateModel(msg, &cmds, &m.timer) instead of:
// m.timer, cmd = m.timer.Update(msg); cmds = append(cmds, cmd)
// simplified for brevity
newM := (*m).Update()
*m = newM
}

func test() {
var x1 MyModel[timerModel] = timerModel{}
var x2 MyModel[viewportModel] = viewportModel{}

updateModel(&x1)
updateModel(&x2)
}
```

This is still suboptimal for sure; alternatively `Update` could simply return `tea.Model` which the user then would have to downcast back to component's specific model.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.