Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v2.0.0-beta.2] Custom ValueContainer doesn't work without rendering "children" #2597

Closed
einarq opened this issue May 10, 2018 · 31 comments
Closed
Labels
issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet issue/reviewed Issue has recently been reviewed (mid-2020)

Comments

@einarq
Copy link

einarq commented May 10, 2018

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>
  );
};
@einarq einarq changed the title Custom ValueContainer doesn't work without rendering "children" [v2.0.0-beta.2] Custom ValueContainer doesn't work without rendering "children" May 10, 2018
@einarq
Copy link
Author

einarq commented Jun 6, 2018

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.

@maureenwaggl
Copy link

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!

@jossmac jossmac added the v2 label Jul 5, 2018
@maureenwaggl
Copy link

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>
    );
  };

@andrewdavidcostello
Copy link

andrewdavidcostello commented Oct 4, 2018

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.

@italopessoa
Copy link

italopessoa commented Dec 4, 2018

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

@einarq
Copy link
Author

einarq commented Dec 7, 2018

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

Nope, haven't tried that, sorry

@lou
Copy link

lou commented Jan 8, 2019

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

@lou
Copy link

lou commented Jan 8, 2019

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.

@j-funk
Copy link

j-funk commented Jan 18, 2019

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

@saboya
Copy link

saboya commented Mar 19, 2019

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.

react-select/src/Select.js

Lines 1380 to 1408 in 770ba08

renderInput() {
const {
isDisabled,
isSearchable,
inputId,
inputValue,
tabIndex,
} = this.props;
const { Input } = this.components;
const { inputIsHidden } = this.state;
const id = inputId || this.getElementId('input');
if (!isSearchable) {
// use a dummy input to maintain focus/blur functionality
return (
<DummyInput
id={id}
innerRef={this.getInputRef}
onBlur={this.onInputBlur}
onChange={noop}
onFocus={this.onInputFocus}
readOnly
disabled={isDisabled}
tabIndex={tabIndex}
value=""
/>
);
}

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

@Rall3n
Copy link
Collaborator

Rall3n commented Mar 22, 2019

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>
}

@saboya
Copy link

saboya commented Mar 22, 2019

@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.

@saboya
Copy link

saboya commented Mar 22, 2019

@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.

@carlostxm
Copy link

carlostxm commented Jul 19, 2019

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".

@Valgoku01
Copy link

Valgoku01 commented Oct 9, 2019

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...

@pastinepolenta
Copy link

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?

@brahmdev
Copy link

brahmdev commented Jan 28, 2020

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>
    );

@wesleywong
Copy link

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?

@wesleywong
Copy link

wesleywong commented Feb 6, 2020

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?

@Rall3n
Copy link
Collaborator

Rall3n commented Feb 7, 2020

@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>

@merykozlowska
Copy link

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) 😞

@Rall3n
Copy link
Collaborator

Rall3n commented Feb 15, 2020

@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
Copy link

@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.

@dpiotti
Copy link

dpiotti commented Apr 14, 2020

@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
Copy link
Collaborator

Rall3n commented Apr 15, 2020

@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.

@dpiotti
Copy link

dpiotti commented Apr 15, 2020

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

@gregholst
Copy link

@bladey bladey added issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet and removed v2-deprecated labels May 28, 2020
@bladey bladey added the issue/reviewed Issue has recently been reviewed (mid-2020) label Jun 17, 2020
@ebonow
Copy link
Collaborator

ebonow commented Dec 9, 2020

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.

@Shivraj97
Copy link

Shivraj97 commented Mar 1, 2021

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>
    );

Thank you for this solution, it solved my problem but now I can't see placeholder text. Could you please tell how to render placeholder if no values are selected.

@ashfaqnisar
Copy link

ashfaqnisar commented Sep 16, 2023

To the people who are trying to fix this issue, please upgrade to the latest version (5.7.4). The issue was fixed for me without any additional fixes required.

I have done this directly:

const ValueContainer = ({ children, ...props }: ValueContainerProps<OptionType>): ReactElement => {
  const [values, input] = children as ReactElement[];
  const valueLength = props?.getValue()?.length;

  return (
    <components.ValueContainer {...props}>
      {valueLength > 1 ? (
        <CustomTag label={`${valueLength} Items Selected`} pr={3} />
      ) : (
        values
      )}
      {input}
    </components.ValueContainer>
  );
};

@EloiPampliega
Copy link

To the people who are trying to fix this issue, please upgrade to the latest version (5.7.4). The issue was fixed for me without any additional fixes required.

I have done this directly:

const ValueContainer = ({ children, ...props }: ValueContainerProps<OptionType>): ReactElement => {
  const [values, input] = children as ReactElement[];
  const valueLength = props?.getValue()?.length;

  return (
    <components.ValueContainer {...props}>
      {valueLength > 1 ? (
        <CustomTag label={`${valueLength} Items Selected`} pr={3} />
      ) : (
        values
      )}
      {input}
    </components.ValueContainer>
  );
};

Thanks! Works perfect 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet issue/reviewed Issue has recently been reviewed (mid-2020)
Projects
None yet
Development

No branches or pull requests