thisbeyond / solid-select

The Select component for Solid.
https://solid-select.com
MIT License
172 stars 18 forks source link

How to make a "controlled" Select? #39

Closed futurepaul closed 1 year ago

futurepaul commented 1 year ago

I'm attempting to have an additional row of "tags" below the Select component for quick access to recent tags.

Screenshot 2023-04-26 at 9 45 34 AM

I'm overriding the onChange method like in the Kitchen Sink example. It "works" in that I can click on one of my tags and it's added to the value of the Select... I see the option I've clicked as disabled when I open the menu. But the item doesn't get added to the "text box" view. It can be deleted on backspace, so it seems like it's there logically, but it never shows up visually.

Here's a simplified version of my code:

export function TagEditor() {
    const options: TagItem[] = [
        { id: createUniqueId(), name: "Unknown" },
        { id: createUniqueId(), name: "Alice" },
        { id: createUniqueId(), name: "Bob" },
        { id: createUniqueId(), name: "Carol" },
    ]
    const [values, setValues] = createSignal(options);
    const [selectedValues, setSelectedValues] = createSignal([options[0]]);

    const onChange = (selected: TagItem[]) => {
        setSelectedValues(selected);
        const lastValue = selected[selected.length - 1];
        if (lastValue && !values().includes(lastValue)) {
            setValues([...values(), lastValue]);
        }
    };

    const selectProps = createOptions(options, {
        key: "name",
        disable: (value) => selectedValues().includes(value),
        filterable: true, // Default
        createable: createValue,
    });

    return (
        <>
            <Select
                multiple
                onChange={onChange}
                placeholder="Where's it coming from?"
                {...selectProps}
            />

            {/* My own list of tags */}
            <For each={subtract(values(), selectedValues()).slice(0, 3)}>
                {(contact) => (
                    <div onClick={() => onChange([...selectedValues(), contact])} >{contact.name}</div>
                )}
            </For>
        </>
    )
}
martinpengellyphillips commented 1 year ago

You can use initialValue prop to make the select 'controlled'. So rather than calling onChange in your tag helper you would setSelectedValues. Something like the following (I've also renamed some things to make them clearer):

export function TagEditor() {
  const initialOptions: TagItem[]  = [
    { id: createUniqueId(), name: "Unknown" },
    { id: createUniqueId(), name: "Alice" },
    { id: createUniqueId(), name: "Bob" },
    { id: createUniqueId(), name: "Carol" },
  ];

  const [options, setOptions] = createSignal(initialOptions);
  const [selectedValues, setSelectedValues] = createSignal([initialOptions[0]]);

  const onChange = (selected: TagItem[]) => {
    setSelectedValues(selected);
    const lastValue = selected[selected.length - 1];
    if (lastValue && !options().includes(lastValue)) {
      setOptions([...options(), lastValue]);
    }
  };

  const selectProps = createOptions(options, {
    key: "name",
    disable: (value) => selectedValues().includes(value),
    filterable: true, // Default
    createable: createValue,
  });

  return (
    <>
      <Select
        multiple
        initialValue={selectedValues()}  // This is the line to make the select controlled
        onChange={onChange}
        placeholder="Where's it coming from?"
        {...selectProps}
      />

      {/* My own list of tags */}
      <For each={subtract(options(), selectedValues()).slice(0, 3)}>
        {(contact) => (
          <div
            onClick={() => setSelectedValues([...selectedValues(), contact])}
          >
            {contact.name}
          </div>
        )}
      </For>
    </>
  );
}

I've also updated the Kitchen Sink example with 'quick pick' options: https://solid-select.com/?example=Kitchen%2520Sink

futurepaul commented 1 year ago

Working perfectly, thank you so much!