Skip to content

OptionsFunc ignores Selected Option #339

Closed
@mrtnhwttktc

Description

@mrtnhwttktc

Describe the bug
When using OptionsFunc for a huh.NewSelect[string](), setting the cursor index using .Value(myValue) or option.Selected(true) does not work, leading to the cursor not being at the correct index when the form is rendered.

To Reproduce
Here's two simple examples that demonstrates the issue. This first snippet uses .Value() to set the cursor index when the form renders.

Usually, when using NewSelect, setting the variable passed to .Value() to a valid option sets the cursor index to that option in the Select form.

In this example, both selects should have the second option selected when the form renders:

package main

import (
	"fmt"
	"github.com/charmbracelet/huh"
)

type App struct {
	optionA string
	optionB string
}

func main() {
	app := App{optionA: "fizz", optionB: "bazz"} // manually set the value of optionA and optionB, when the form renders the second option in both select should be selected

	choices := map[string][]string{
		"foo":  {"bar", "fin"},
		"fizz": {"buzz", "bazz"},
	}

	huh.NewForm(
		huh.NewGroup(
			huh.NewSelect[string]().
				Title("Select Option A").
				Options(huh.NewOptions("foo", "fizz")...).
				Value(&app.optionA).
				Key("a"),
			huh.NewSelect[string]().
				Title("Select Option B").
				OptionsFunc(func() []huh.Option[string] {
					return huh.NewOptions(choices[app.optionA]...)
				}, &app.optionA).
				Value(&app.optionB),
		),
	).Run()
	fmt.Println("Selected options:", app)
}

Results in:

┃ Select Option A
┃   foo
┃ > fizz

  Select Option B
  > buzz
    bazz

For optionA, the value set when instantiating the App struct "fizz" is selected as expected, but not for OptionB, where the first option of the list is selected.

I then tried explicitly setting the selected option by looping through the options and using .Selected(true) in the OptionsFunc function before returning the options list:

package main

import (
	"fmt"
	"github.com/charmbracelet/huh"
)

type App struct {
	optionA string
	optionB string
}

func main() {
	app := App{optionA: "fizz", optionB: "bazz"}

	choices := map[string][]string{
		"foo":  {"bar", "fin"},
		"fizz": {"buzz", "bazz"},
	}

	huh.NewForm(
		huh.NewGroup(
			huh.NewSelect[string]().
				Title("Select Option A").
				Options(huh.NewOptions("foo", "fizz")...).
				Value(&app.optionA).
				Key("a"),
			huh.NewSelect[string]().
				Title("Select Option B").
				OptionsFunc(func() []huh.Option[string] {
					options := huh.NewOptions(choices[app.optionA]...)
					for _, option := range options {
						if option.Value == "bazz" {
							option.Selected(true)
						}
					}
					return options
				}, &app.optionA).
				Value(&app.optionB),
		),
	).Run()
	fmt.Println("Selected options:", app)
}

Results in:

┃ Select Option A
┃   foo
┃ > fizz

  Select Option B
  > buzz
    bazz

Just like the previous code, the selected option in the second select form is not the value set manually in app.optionB or when creating the list of options in OptionsFunc.

Expected behavior
Using the .Selected() method of an Option to select it in OptionsFunc should select that option when rendering the form.

When using the OptionsFunc, the value passed in Value() of the Select form should be used as the selected/cursor position in the same way it does with a Select form not using OptionsFunc. This is not so much an issue if there is a way to set the selected option in the OptionsFunc.

Additional context
Until recently I was using v4.3 and encountered a situation where I wanted to generate options for a select based on the input of a previous select. I was quite happy to see that a new version was available with exactly what I needed!

I use a struct that get values from flags and then use huh to get a nice complete/update/confirm form before proceeding. Using the fields of the struct in the .Value() method allowed me to easily set the cursor to the option corresponding to the value set in the flags so that's how I have been doing it, not sure if there is a better way.

I wasn't too surprised by the fact that the value set in Value() is ignored when using OptionsFunc, but was surprised by the fact that manually setting the select option with .Selected(true) in OptionsFunc had no effect.

Before posting this PR I asked on the discord server and another user took a look at it. They noticed that if after selecting the second option in the second select you shift+tab back to the previous select and move the cursor around, the cursor on the second select stay on the second option.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingselect

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions