React-select: [v2.0.0-beta.2] Custom ValueContainer doesn't work without rendering "children"

Created on 10 May 2018  路  28Comments  路  Source: JedWatson/react-select

I'm trying to use the ValueContainer option to render the selected option differently. Seems I cannot do that and still let the selected option (ValueContainer) be clickable and open the menu without also rendering the children prop. However, in rendering the children prop, I'm basically rendering the selected option twice.
Clicking the arrow still works though.

Basically, this works fine (example from docs):

const ValueContainer = ({children, ...props}) => (
  <components.ValueContainer {...props}>{children}</components.ValueContainer>
);

This does not work:

const ValueContainer = ({children, ...props}) => {
  if (!props.hasValue) {
    return <components.ValueContainer {...props}>{children}</components.ValueContainer>;
  }

  const value = props.getValue()[0];
  return (
    <components.ValueContainer {...props}>
      <NodeItem node={value} />
    </components.ValueContainer>
  );
};
issubug-unconfirmed issureviewed

Most helpful comment

You could filter out the input from children so you can still keep standard functionality:

const ValueContainer = ({children, ...props}) =>{
    var selectInput = React.Children.toArray(children).find((input) => input.type.name === "Input" || input.type.name === "DummyInput");

    return <components.ValueContainer { ...props }>
        { /* Whatever you want to render */ }
        {selectInput}
   </components.ValueContainer>
}

All 28 comments

Any update on this? Or am I doing something wrong? I don't think I will be able to upgrade to v2 until this is resolved.

I'm running into this issue as well. I even tried wrapping the children in a div with visibility hidden or display none and neither kept the click event. Is there a good workaround or solution for this issue? Thanks!

I was able to get a workaround to work in the meantime. Note that this is for a select that has isSearchable=false so it's using the dummy input component, but it may work with searchable selects with more CSS tweaks to hide the input component.

With this approach, we render the children that has the focus/blur events and just hide the children content. For non-searchable selects, the dummy input should already be hidden, so in this case we just need to hide the value.

const ValueWrapper = styled.div`
  .dummy-input-wrapper {
    .Select__single-value {
      display: none;
    }
  }
`;

  const ValueContainer = (props) => {
    const { selectProps: { value: { label } }, children } = props;
    return (
      <components.ValueContainer {...props}>
        <ValueWrapper>
          <span className="formatted-value">
            <span>{category}:</span>
            <LabelValue>{label}</LabelValue>
          </span>
          {/* This is a hack to make the entire select clickable,
            rather than just the dropdown selector */}
          <span className="dummy-input-wrapper">
            {children}
          </span>
        </ValueWrapper>
      </components.ValueContainer>
    );
  };

This may or may not help but I found in my case it was the focused prop on the multivalue child that causes this.

let multiValueProps = { ...props }

multiValueProps.components = {
  Container: components.MultiValueContainer,
  Label: components.MultiValueLabel,
  Remove: components.MultiValueRemove
}

I had to add in multiValueProps.isFocused = false

Inside my custom ValueContainer is:

[(
        <components.MultiValue {...multiValueProps}>
            {content}
        </components.MultiValue>
), children[1]]

As children[1] is always the input.

So I assume you still need some kind of multi value in there or something which states it at least isn't focused as it will be grabbing the focus over the input on click.

Would be nice to make this easier/get some clarification.

hey @einarq did you know how to change the isRtl prop value different from the SelectContainer component?

hey @einarq did you know how to change the isRtl prop value different from the SelectContainer component?

Nope, haven't tried that, sorry

I'm running into a very similar issue.
It's not possible to click on the input when setting a custom ValueContainer and a defaultValue.
Everything is working as expected after clearing the input or not setting a defaultValue.
Any idea why ?
Demo https://codesandbox.io/s/q3x56y176j

I managed to make it work thanks to @maureenwaggl hack https://codesandbox.io/s/6w1nxnyqkz.
But it seems that a better patch is needed here.

I am also having this exact issue, breaks onFocus and onBlur props on main <ReactSelect> component. Version 2.2.0

I believe this is because react-select uses an internal DummyInput to attach handlers in the default ValueContainer, but there's no way to recreate this input with the available props in ValueContainerProps.

https://github.com/JedWatson/react-select/blob/770ba08c6fcaba5190b0df9dfd4e2da98a2fef46/src/Select.js#L1380-L1408

There should be a inputComponent or inputRef + inputProps to attach to an input to handle this.

You could filter out the input from children so you can still keep standard functionality:

const ValueContainer = ({children, ...props}) =>{
    var selectInput = React.Children.toArray(children).find((input) => input.type.name === "Input" || input.type.name === "DummyInput");

    return <components.ValueContainer { ...props }>
        { /* Whatever you want to render */ }
        {selectInput}
   </components.ValueContainer>
}

@Rall3n that's a nice solution, hadn't thought about that. For now I used a workaround with the MultiValue container, only rendering the first child through CSS.

@Rall3n FYI, I tried the suggested approach, still doesn't work. The input is being rendered correctly but the functionality still isn't the same (onBlur doesn't close the options, etc).

Can't investigate this any further right now, so I'll keep my MultiValue hack until I can dig into this further.

Be careful with the solution proposed by @Rall3n as it might not work in production because of minification.

To fix the problem I have added the displayName property manually to the Input component to have the component name in production:

export const Input: React.FC<InputProps> = props => (
  <components.Input {...props} />
);
Input.displayName = 'Input';

Then you can filter your Input component using child.type.displayName === "Input" instead of child.type.name === "Input".

The bug still exists in the version 3.0.8
An easy fix is to set the property blurInputOnSelect to true.
The downside of it though is that it closes the drop-down every time the user makes a selection, even with the property closeMenuOnSelect set to false...

You could filter out the input from children so you can still keep standard functionality:

const ValueContainer = ({children, ...props}) =>{
    var selectInput = React.Children.toArray(children).find((input) => input.type.name === "Input" || input.type.name === "DummyInput");

    return <components.ValueContainer { ...props }>
        { /* Whatever you want to render */ }
        {selectInput}
   </components.ValueContainer>
}

I found out that checking for type.name does not work well in case of production builds with component name mangling. Is there a more reliable way?

We were also facing this problem for a while.
And below is the working code for us.

  1. We added inputId prop to <Select> component like below:
<Select
          className='select'
          classNamePrefix='filter'
          inputId='clickableInput'
          isMulti
          components={{ ValueContainer }}
         ....
        />
  1. And below is our return from ValueContainer:
return (
      <components.ValueContainer {...props}>
       // code for some custom element if you need goes here
        {React.Children.map(children, (child) => {
          return child.props.id === 'clickableInput' ? child : null;
        })}
      </components.ValueContainer>
    );
const ValueContainer = ({children, ...props}) =>{
    var selectInput = React.Children.toArray(children).find((input) => input.type.name === "Input" || input.type.name === "DummyInput");

    return <components.ValueContainer { ...props }>
        { /* Whatever you want to render */ }
        {selectInput}
   </components.ValueContainer>
}

Thanks @Rall3n . This worked for local but not after production build. Is there a way we can set displayName for the components input?

Be careful with the solution proposed by @Rall3n as it might not work in production because of minification.

To fix the problem I have added the displayName property manually to the Input component to have the component name in production:

export const Input: React.FC<InputProps> = props => (
  <components.Input {...props} />
);
Input.displayName = 'Input';

Then you can filter your Input component using child.type.displayName === "Input" instead of child.type.name === "Input".

@carlostxm May I know how to insert this code? Do we need to fork the library?

@wesleywong @carlostxm There is a better solution that does not require you to add a displayName to the Input component.

You can check if the type of the component is the same as components.Input. That should work with minification

const CustomValueContainer = ({ children, ...props }) => <components.ValueContainer {...props}>
    {React.Children.map(children, (child) => child.type === components.Input ? child : null)}
</components.ValueContainer>

Thank you, @brahmdev! This worked for me. Also if you'd like to make the ValueContainer component more reusable you can compare the child's id to props.selectProps.inputId.

@Rall3n This is nice but doesn't work for the DummyInput (so for example when isSearchable === false) :disappointed:

@merykozlowska Then either replace components.Input with components.DummyInput or if you need both put them in an array and use Array.prototype.indexOf to check if the type is in the array.

<components.ValueContainer>
{React.Children.map(children, (child) => [components.Input, components.DummyInput].indexOf(child.type) >= 0 ? child : null)}
</components.ValueContainer>

@merykozlowska Then either replace components.Input with components.DummyInput or if you need both put them in an array and use Array.prototype.indexOf to check if the type is in the array.

I would've done exactly that but... DummyInput is not exposed on components or - from what I've seen - exported from the library in any other way.
For now brahmdev's solution with inputId works well for me so I'll just use that.

@Rall3n I found that I also needed to add 'w' to the array. After building my project I noticed the name for the dummyInputs got converted to w.

This worked for me:

const childsToRender = React.Children.toArray(children).filter((child) => ['Input', 'DummyInput', 'Placeholder', 'w'].indexOf(child.type.name) >= 0);

@Rall3n I found that I also needed to add 'w' to the array. After building my project I noticed the name for the dummyInputs got converted to w.

This worked for me:

const childsToRender = React.Children.toArray(children).filter((child) => ['Input', 'DummyInput', 'Placeholder', 'w'].indexOf(child.type.name) >= 0);

@dpiotti I would not recommend to filter by type.name because of minification (as you experienced yourself). Instead filter by type and compare with components.

But I would now recommend to do reverse filtering. Instead of filtering for the components you want to keep, you filter for the components you do not want to keep and prohibit them from rendering.

const ValueContainer = ({children, ...props}) => <components.ValueContainer {...props}>
  {React.Children.map(children, (child) => (child && [components.SingleValue].indexOf(child.type) === -1) ? child : null)}
</components.ValueContainer>;

This also circumvents the problem that DummyInput is not exported with components.

@Rall3n Good note. Reverse filtering was the way to go for me

I will be closing out this ticket to continue focusing on existing bugs and issues. I can reopen this if necessary but it seems that @Rall3n has identified a working solution. Thank you!

@gregholst if you would still like assistance, please feel free to reply to this with a codesandbox, so we can take a look into this further with you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

coder-guy22296 picture coder-guy22296  路  3Comments

MindRave picture MindRave  路  3Comments

batusai513 picture batusai513  路  3Comments

x-yuri picture x-yuri  路  3Comments

ericj17 picture ericj17  路  3Comments