admin管理员组

文章数量:1402315

I'm using react-select and have made a few customizations to it. I tried to modify the ValueContainer and the SelectContainer ponents which cause some issues. The dropdown wont close when I click outside of it after having selected a value. I'm wondering if anyone can see what I have done wrong here?

I'm assuming that the dropdown wont close because the onBlur event for the Input doesn't trigger. E.g the input doesn't regain its focus after i click a value with closeMenuOnSelect = true. I am also assuming that this is because react-select cant find the input for some reason because I've modified the structure. Might be missing some refs or something but I can't understand where to get them and where to put them. Anyone know?

Here is my custom Select ponent:

const ReactSelect: React.FC<ReactSelectProps> = ({
  backgroundColor,
  isSearchable = false,
  placeholder = '',
  size,
  grow,
  className,
  required,
  label,
  variant = 'underline',
  dataTestId,
  ...props
}) => {
  const filterConfig = {
    ignoreCase: true,
    ignoreAccents: true,
    matchFromStart: false,
    stringify: (option: any) => `${option.label}`,
    trim: true,
  };

  const [showIsRequired, setShowIsRequired] = useState(
    !props.defaultValue && required
  );

  const handleOnChange = (newValue: any, actionMeta: ActionMeta<any>) => {
    setShowIsRequired(!newValue && required);
    if (props.onChange) {
      props.onChange(newValue, actionMeta);
    }
  };

  const properties = {
    ...props,
    className: className,
    onChange: handleOnChange,
    isSearchable: isSearchable,
    styles: customStyles(size, backgroundColor, grow, showIsRequired, variant),
    menuPlacement: 'auto' as MenuPlacement,
    placeholder: placeholder,
    noOptionsMessage: () => t`Ingen elementer funnet`,
    loadingMessage: () => t`Laster...`,
    filterOption: createFilter(filterConfig),
    closeMenuOnSelect: !props.isMulti,
    hideSelectedOptions: false,
    ponents: {
      ValueContainer: (inputProps: ValueContainerProps) => (
        <ValueContainer required={required} label={label} {...inputProps} />
      ),
      Option,
      SelectContainer: (containerProps: ContainerProps) => (
        <SelectContainer dataTestId={dataTestId} {...containerProps} />
      ),
    },
  };

  if ('value' in props) {
    return <StateManagedSelect {...properties} />;
  }

  return <Select {...properties} />;
};

And here are my custom <SelectContainer> and <ValueContainer>:

const ValueContainer = ({ children, label, required, ...rest }: any) => {
  const labelFloatingStyle = {
    top: '0.30rem',
    left: '0.6rem',
    transform: 'translate(0, 0) scale(1)',
    fontSize: '0.75rem',
    color: 'hsl(236, 91%, 9%)',
    fontWeight: '500',
  };
  const labelStyle = {
    top: '50%',
    left: '0.75rem',
    transform: 'translate(0, -50%) scale(1)',
    fontSize: '1rem',
    color: 'hsl(0, 0%, 45%)',
  };
  const requiredDotStyles = {
    color: 'hsl(35, 100%, 43%)',
    fontSize: '1.5rem',
    display: rest.hasValue ? 'none' : 'inline',
  };
  const getLabelStyles = () =>
    rest.hasValue || rest.isFocused ? labelFloatingStyle : labelStyle;
  return (
    <ponents.ValueContainer {...rest}>
      {children}
      {label && (
        <label
          style={{
            position: 'absolute',
            transformOrigin: 'left bottom',
            transition: 'all 0.2s',
            display: 'flex',
            alignItems: 'center',
            ...getLabelStyles(),
          }}
        >
          {label} {required && <span style={requiredDotStyles}>*</span>}
        </label>
      )}
    </ponents.ValueContainer>
  );
};

const SelectContainer = ({ dataTestId, ...rest }: any) => (
  <div data-test-id={dataTestId}>
    <ponents.SelectContainer {...rest} />
  </div>
);

I'm using react-select and have made a few customizations to it. I tried to modify the ValueContainer and the SelectContainer ponents which cause some issues. The dropdown wont close when I click outside of it after having selected a value. I'm wondering if anyone can see what I have done wrong here?

I'm assuming that the dropdown wont close because the onBlur event for the Input doesn't trigger. E.g the input doesn't regain its focus after i click a value with closeMenuOnSelect = true. I am also assuming that this is because react-select cant find the input for some reason because I've modified the structure. Might be missing some refs or something but I can't understand where to get them and where to put them. Anyone know?

Here is my custom Select ponent:

const ReactSelect: React.FC<ReactSelectProps> = ({
  backgroundColor,
  isSearchable = false,
  placeholder = '',
  size,
  grow,
  className,
  required,
  label,
  variant = 'underline',
  dataTestId,
  ...props
}) => {
  const filterConfig = {
    ignoreCase: true,
    ignoreAccents: true,
    matchFromStart: false,
    stringify: (option: any) => `${option.label}`,
    trim: true,
  };

  const [showIsRequired, setShowIsRequired] = useState(
    !props.defaultValue && required
  );

  const handleOnChange = (newValue: any, actionMeta: ActionMeta<any>) => {
    setShowIsRequired(!newValue && required);
    if (props.onChange) {
      props.onChange(newValue, actionMeta);
    }
  };

  const properties = {
    ...props,
    className: className,
    onChange: handleOnChange,
    isSearchable: isSearchable,
    styles: customStyles(size, backgroundColor, grow, showIsRequired, variant),
    menuPlacement: 'auto' as MenuPlacement,
    placeholder: placeholder,
    noOptionsMessage: () => t`Ingen elementer funnet`,
    loadingMessage: () => t`Laster...`,
    filterOption: createFilter(filterConfig),
    closeMenuOnSelect: !props.isMulti,
    hideSelectedOptions: false,
    ponents: {
      ValueContainer: (inputProps: ValueContainerProps) => (
        <ValueContainer required={required} label={label} {...inputProps} />
      ),
      Option,
      SelectContainer: (containerProps: ContainerProps) => (
        <SelectContainer dataTestId={dataTestId} {...containerProps} />
      ),
    },
  };

  if ('value' in props) {
    return <StateManagedSelect {...properties} />;
  }

  return <Select {...properties} />;
};

And here are my custom <SelectContainer> and <ValueContainer>:

const ValueContainer = ({ children, label, required, ...rest }: any) => {
  const labelFloatingStyle = {
    top: '0.30rem',
    left: '0.6rem',
    transform: 'translate(0, 0) scale(1)',
    fontSize: '0.75rem',
    color: 'hsl(236, 91%, 9%)',
    fontWeight: '500',
  };
  const labelStyle = {
    top: '50%',
    left: '0.75rem',
    transform: 'translate(0, -50%) scale(1)',
    fontSize: '1rem',
    color: 'hsl(0, 0%, 45%)',
  };
  const requiredDotStyles = {
    color: 'hsl(35, 100%, 43%)',
    fontSize: '1.5rem',
    display: rest.hasValue ? 'none' : 'inline',
  };
  const getLabelStyles = () =>
    rest.hasValue || rest.isFocused ? labelFloatingStyle : labelStyle;
  return (
    <ponents.ValueContainer {...rest}>
      {children}
      {label && (
        <label
          style={{
            position: 'absolute',
            transformOrigin: 'left bottom',
            transition: 'all 0.2s',
            display: 'flex',
            alignItems: 'center',
            ...getLabelStyles(),
          }}
        >
          {label} {required && <span style={requiredDotStyles}>*</span>}
        </label>
      )}
    </ponents.ValueContainer>
  );
};

const SelectContainer = ({ dataTestId, ...rest }: any) => (
  <div data-test-id={dataTestId}>
    <ponents.SelectContainer {...rest} />
  </div>
);
Share Improve this question asked Feb 15, 2022 at 14:51 mTvmTv 1,3964 gold badges22 silver badges41 bronze badges 2
  • were you able to fixed this? Got the same issue too. – ruin3936 Commented Mar 9, 2022 at 9:00
  • Yes. Check out the answer i posted – mTv Commented Mar 9, 2022 at 10:41
Add a ment  | 

4 Answers 4

Reset to default 2

The problem was with how I sent in the custom ponents. You shouldn't pass the props there. I think that might ruin some of the important ReactSelect props like refs and such.

Send the ponents in like this instead. Define your custom properties in this object as well:

  const properties = {
    ...props,
    className: className,
    onChange: handleOnChange,
    isSearchable: isSearchable,
    styles: customStyles(size, backgroundColor, grow, showIsRequired, variant),
    menuPlacement: 'auto' as MenuPlacement,
    placeholder: placeholder,
    noOptionsMessage: () => t`Ingen elementer funnet`,
    loadingMessage: () => t`Laster...`,
    filterOption: createFilter(filterConfig),
    closeMenuOnSelect: !props.isMulti,
    hideSelectedOptions: false,
    label: label,
    required: required,
    dataTestId: dataTestId,
    ponents: {
      ValueContainer,
      Option,
      SelectContainer,
    },
  };

  if ('value' in props) {
    return <StateManagedSelect {...properties} />;
  }

  return <Select {...properties} />;

Then you can get these custom props in the custom ponent using props.selectProps like this:

const ValueContainer = ({ children, ...rest }: any) => {
  const { label, required } = rest.selectProps; // HERE
  const labelFloatingStyle = {
    top: '0.20rem',
    left: '0.6rem',
    transform: 'translate(0, 0) scale(1)',
    fontSize: '0.75rem',
    color: 'hsl(236, 91%, 9%)',
    fontWeight: '500',
  };
  const labelStyle = {
    top: '50%',
    left: '0.75rem',
    transform: 'translate(0, -50%) scale(1)',
    fontSize: '1rem',
    color: 'hsl(0, 0%, 45%)',
  };
  const requiredDotStyles = {
    color: 'hsl(35, 100%, 43%)',
    fontSize: '1.5rem',
    display: rest.hasValue ? 'none' : 'inline',
  };

  const getLabelStyles = () =>
    rest.hasValue || rest.isFocused ? labelFloatingStyle : labelStyle;

  return (
    <ponents.ValueContainer {...rest}>
      {children}
      {label && (
        <label
          style={{
            position: 'absolute',
            transformOrigin: 'left bottom',
            transition: 'all 0.2s',
            display: 'flex',
            alignItems: 'center',
            cursor: 'pointer',
            ...getLabelStyles(),
          }}
        >
          {label} {required && <span style={requiredDotStyles}>*</span>}
        </label>
      )}
    </ponents.ValueContainer>
  );
};

Here is a solution that is taken from the accepted one which is more generic.

The code that was breaking for me when trying to click outside:

    <Select
        isMulti
        name={labelText}
        placeholder={placeholder}
        defaultValue={defaultValues}
        options={options}
        onChange={handleChange}
        onMenuClose={onMenuClose}
        ponents={{
          Option,
          ValueContainer: (props) => (
            <ValueContainer {...props} maxItems={maxItems} label={label} />
          )
        }}
/>

so right inside the ponent, I created a valueContainerProps object and passed the props to the select ponent directly by spreading them. These props are accessible inside the selectProps param of the props received by ValueContainer

// .... 
   const valueContainerProps = {
     maxItems,
     label
   };

  return (
    <>
      <Select
        isMulti
        name={label}
        placeholder={placeholder}
        defaultValue={defaultValues}
        options={options}
        onChange={handleChange}
        onMenuClose={onMenuClose}
        ponents={{
          Option,
          ValueContainer
        }}
        { ...valueContainerProps }
      />
     </>)

and then -

const ValueContainer = ({ children, ...props }: ValueContainerProps<LabelAndValue, true>) => {
  const { maxItems = 2, label = 'options' } = props.selectProps as any;
  // ... rest of the code

hope this helps.

The problem is in the ValueContainer, since the focus is lost from our selector, the easiest way to solve this problem is to add focus to the ValueContainer

const refSelect = useRef<any>(null);
    
const ValueContainer: React.FC<CustomValueContainerProps> = ({
  ...props
}) => {
  let [values, input] = props.children;

    if (refSelect?.current) {
      refSelect.current.focus();
    }

  return (
    <ponents.ValueContainer {...props}>
      {input}
      {values}
    </ponents.ValueContainer>
  );
};
    
<CustomSelect
    value={value}
    classNamePrefix={'Select'}
    options={options}
    onChange={handleChange}
    closeMenuOnSelect={closeMenuOnSelect}
    ref={refSelect}
    ponents={{
      ValueContainer,
    }}
/>

As per Dmytry's answer, the only thing I would change is changing when the select should actually have the focus. Otherwise, the focus will always be hijacked by your ponent on any page you use it.

const refSelect = useRef<any>(null);
    
const ValueContainer: React.FC<CustomValueContainerProps> = ({
  ...props
}) => {
  let [values, input] = props.children;

//only gain focus if the menu is open. This also helps with cross click close when closeMenuOnSelect is false
    if (refSelect?.current && props.selectProps.menuIsOpen) {
            refSelect.current.focus();
        }

  return (
    <ponents.ValueContainer {...props}>
      {input}
      {values}
    </ponents.ValueContainer>
  );
};
    
<CustomSelect
    value={value}
    classNamePrefix={'Select'}
    options={options}
    onChange={handleChange}
    closeMenuOnSelect={closeMenuOnSelect}
    ref={refSelect}
    ponents={{
      ValueContainer,
    }}
/>

本文标签: javascriptreactselect wont close when clicking outsideStack Overflow