JedWatson / react-select

The Select Component for React.js
https://react-select.com/
MIT License
27.56k stars 4.12k forks source link

Feature Request: merge styles with provided by default #4743

Closed brandonchinn178 closed 2 years ago

brandonchinn178 commented 3 years ago

My use case

I'm adding a multiselect field to a form, that's basically intended to be a nicer version of a <textarea> with comma separated values. As such, I want the cursor to still show the text cursor when hovering over the input, but not over the multiValue elements. Specifically, I have the following:

styles={{
  valueContainer: (provided: object) => ({
    ...provided,
    cursor: 'text',
  }),
  multiValueLabel: (provided: object) => ({
    ...provided,
    cursor: 'default',
  }),
  multiValueRemove: (provided: object) => ({
    ...provided,
    cursor: 'pointer',
  }),
}}

I find it rather annoying to have to repeat ...provided every time; in fact, I can't personally think of an instance where I would want to throw away provided entirely and start completely from scratch. Most if not all of the time, I want to add styles on top of react-select's built-in styles.

Proposal

  1. By default, merge styles with provided. () => ({ foo: bar }) should be equivalent to (provided) => ({ ...provided, foo: bar })
  2. If a special override key is set to true, don't merge. () => ({ override: true, foo: bar }) is equivalent to today's () => ({ foo: bar })
  3. As a shorthand, a plain object may be specified. valueContainer: { foo: bar } should be equivalent to valueContainer: () => ({ foo: bar }) which merges { foo: bar } with provided, per (1)
ebonow commented 2 years ago

@brandonchinn178 I had originally left a comment here, but looked more at the PR and will instead move the conversation there.

ebonow commented 2 years ago

@brandonchinn178 I wanted to let you know this was discussed with the team and thought that this would add more complexity and breaking changes versus the ability to save some time writing styles.

I didn't want to dismiss though without providing a viable solution to this. In this case, it's possible to simply write a hook to do exactly what you are asking so that the user does not need to write out the provided styles.

Here is a codesandbox demonstrating it in action. https://codesandbox.io/s/react-select-usestylesmerge-hook-3kjxo

// Custom hook used to merge styles to avoid having to spread the existing 
// styled props for the styling function. If the returned style has the property 
// "override", it will not receive the provided styles and will only be styled with the defined properties

const useStylesMerge = (styles) => {
  const mergedStyles = Object.entries(styles).map(
    ([componentName, componentStyle]) => {
      const mergedStyle = (provided, state) => {
        const { override, ...rest } = componentStyle(state);
        return {
          ...(!override && provided),
          ...rest
        };
      };

      return [componentName, mergedStyle];
    }
  );

  return Object.fromEntries(mergedStyles);
};

My hope is that we are able to use hooks more to add user specific functionality like this and for other things.

brandonchinn178 commented 2 years ago

Makes sense. Thanks for following up!

Just to be clear, that's not a React hook or hooking into any React-Select stuff, it's just a function to use like

<Select styles={useMerged(styles)} ... />

right?

ebonow commented 2 years ago

I guess technically it's not a hook since it's not stateful so it is just a function that achieves the same functionality.