Cannot Init correctly when there are multiple Models
Charliego3 opened this issue · 2 comments
Describe the bug
As the title says, only tea.NewProgram(model)
will call the Init()
function when there are multiple Models, and the Model returned by the Update
function has not been properly Init.
In the following example, secondModel
returned by firstModel.Update
does not call Init
, when the return value of firstModel.Update
is changed to return secondModel{s}, tea.Batch(tea.Println( f.input.Value()), s.Tick)
, spinner.Tick
will be consumed correctly. While this allows the program to run normally, it shouldn't be.
Setup
- OS: macOS 13.5
- Shell: fish
- Terminal Emulator: [kitty, Terminal]
Source Code
package main
import (
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"testing"
)
type firstModel struct {
input textinput.Model
done bool
}
func (f firstModel) Init() tea.Cmd {
return textinput.Blink
}
func (f firstModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch k := msg.(type) {
case tea.KeyMsg:
if k.String() == "enter" {
f.done = true
s := spinner.New(spinner.WithSpinner(spinner.Globe))
//return secondModel{s}, tea.Batch(tea.Println(f.input.Value()), s.Tick)
return secondModel{s}, tea.Println(f.input.Value())
}
}
var cmd tea.Cmd
f.input, cmd = f.input.Update(msg)
return f, cmd
}
func (f firstModel) View() string {
if f.done {
return ""
}
return f.input.View() + "\n"
}
type secondModel struct {
spinner spinner.Model
}
func (s secondModel) Init() tea.Cmd {
return s.spinner.Tick
}
func (s secondModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch k := msg.(type) {
case tea.KeyMsg:
if k.String() == "q" {
return s, tea.Quit
}
}
var cmd tea.Cmd
s.spinner, cmd = s.spinner.Update(msg)
return s, cmd
}
func (s secondModel) View() string {
return s.spinner.View() + " wait a moment\n"
}
func TestMultiModel(t *testing.T) {
ti := textinput.New()
ti.Placeholder = "please enter something"
ti.Focus()
ti.CharLimit = 200
ti.Width = 60
ti.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#870004"))
ti.TextStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#005a2c"))
_, _ = tea.NewProgram(firstModel{input: ti}).Run()
}
Expected behavior
Hope Init can be called correctly. Instead of returning additional spinner.Tick
Cmd when returning secondModel
Screenshots
2023-08-21.15.19.53.mov
Hey! I think there is misunderstanding going on
Purpose of tea.Model
returned by Update
is state updating, internally it replaces model it was called with. Notice firstModel
isn't pointer type, but rather value type meaning fields won't be updated as it's effectively a copy of model and not "real" model.
What I'm implying here is that bubbletea
doesn't have to and won't ever compare previous model with returned model, Init
is called exactly once for root model, for child models it is your choice when, how and what do you call, bubbletea
has no control over anything that isn't root model.
As a rule of a thumb, you should never return different tea.Model
in Update
than function receiver one.
Code that replaces (updates) model in bubbletea loop:
Line 398 in 91dd120
As of what you are trying to make you might want to try out my library for bubbletea
called reactea
, it supports multiple views and such, might be an overkill for what you are trying to do.
https://github.com/londek/reactea/tree/v0.4.2
Best regards
Thank you very much, you are right, although this can achieve different pages, but I should not do this, reactea is I want.