JedWatson / react-select

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

Key press issue with custom Control #4279

Closed jbyomuhangi closed 3 years ago

jbyomuhangi commented 3 years ago

I'm using react-select v3.1 and I'm experiencing some very odd behavior, not sure if I'm doing something wrong or if there is a bug.

Scenario : I am making a custom select and I want to create a custom Control that the user can type in to perform searches.

Expected behavior: User can type normally in the custom Control to search for an item to select

Current behavior: Custom Control is made but the typing experience is not working correctly. Some key presses aren't being triggered in the custom control such as backspace, space-bar and delete. Here is some code that illustrates what I am currently doing and the result (this is just for illustration):

import React from "react";
import Select from "react-select";

const options = [
  { value: "chocolate", label: "Chocolate" },
  { value: "strawberry", label: "Strawberry" },
  { value: "vanilla", label: "Vanilla" },
];

const MyCustomControl = () => {
  return <input />;
};

class App extends React.Component {
  state = {
    selectedOption: null,
  };
  handleChange = (selectedOption) => {
    this.setState({ selectedOption }, () =>
      console.log(`Option selected:`, this.state.selectedOption)
    );
  };
  render() {
    const { selectedOption } = this.state;

    return (
      <Select
        value={selectedOption}
        onChange={this.handleChange}
        isSearchable={true}
        options={options}
        components={{
          Control: MyCustomControl,
        }}
      />
    );
  }
}

export default App;
jbyomuhangi commented 3 years ago

Seems like there are some key press events that are being blocked from reaching the custom Control component, I changed MyCustomControl as follows to log every time the onChange event fires, and it doesn't fire when some keys are hit (space-bar, backspace, delete for example), but it does fire with others (the letters on the keyboard for example)

const MyCustomControl = (props) => {
  return (
    <components.Control {...props}>
      <input type="text" onChange={() => console.log("change is happening")} />
    </components.Control>
  );
};
kirthiprakash commented 3 years ago

This could be because the input element is not controlled. Trying setting the value on each onChange event.

Also if you want to handle backspace, enter, delete, try onKeyDown or onKeyUp to handle those events. onChange might not get triggered on those keys.

jbyomuhangi commented 3 years ago

But that's the thing, onChange should be getting triggered, It gets triggered on a normal input element when you try do all that. To my understanding the only events that should be stopped from propagation are the ArrowUp, ArrowDown and Enter key presses, and this should only happen when the select menu is open to allow for keyboard navigation and option selection right? I think these other keys are being stopped as well somewhere in react-select, causing this problem.

ebonow commented 3 years ago

Greetings @jbyomuhangi ,

I believe part of the issue here might be your custom Control component. You seem to be replacing the component with an input which is not expected behavior.

Per the documents

Control The second highest level wrapper around the components. It is responsible for the positioning of the ValueContainer and IndicatorsContainer. It is followed by the Menu.

As for why these keys do not work as expected, the Select Container has keyDown bindings to control special behavior which is then used to trigger different events within the control. You can see the complete list of keys that are bound here

That said, is there some kind of behavior you are trying to accomplish by passing in an input as you are for the Control component?

jbyomuhangi commented 3 years ago

HI @ebonow ,

Yeah, so I have an input component that has some complex logic behind it, it's a rich text editor of sorts. I want this editor to be able to trigger the select menu given specific conditions, so I thought I would be able to replace the control to open the menu and write my own logic to handle opening and closing it. Basically I wanted to switch out the area where the user would type in a searchable select for my own component, but doing so doesn't seem to be possible.

ebonow commented 3 years ago

Have you tried replacing the Input component instead of the Control? Also, you should be able to control the menuIsOpen prop in state without having to write a custom Control component.

If you provide a codesandbox, I might be able to offer whatever insight I can into the functionality you are trying to achieve, but I have come across some difficulty before in the past trying to work with this component as it frustratingly does not provide access to the same common props as other custom components.

jbyomuhangi commented 3 years ago

Yeah, tried making it the Input instead of the Control but same thing happens. Here's a link to the sandbox I just made that simulates a crude example of what I'm trying to do: sandbox

Triggering the opening and selection works no problem, but the input itself breaks down and is unable to receive event like "backspace" and "delete"

ebonow commented 3 years ago

If you do use the Input component, you could pass the props along and they will be applied directly to the Input. One example would be passing any kind of attributes you would want to the input, for example, maxLength...

const Input = (props) => {
  const { maxLength } = props.selectProps;
  const inputProps = { ...props, maxLength };

  return (
    <components.Input {...inputProps} />
  );
};

And here is a sandbox for it: https://codesandbox.io/s/react-select-custom-input-yir19

Conceivably, you should be able to pass other attributes or events but I am not sure if you would be able to recreate the custom logic you are applying to your existing inputs without too much effort.

Hope this helps some.

ebonow commented 3 years ago

If you have any other questions, feel free to follow up and we can always reopen this if necessary.

jbyomuhangi commented 3 years ago

I think I have a better way to illustrate the problem and what I am trying to do. Here is the updated code sandbox link.

In this example, I want to open the menu by clicking something, could be anything, and still have the normal behavior of the selector. Given the selector is not searchable the docs say this:

If the select is not searchable, a dummy input is rendered instead

It appears that this dummy input that is rendered when a selector is not searchable has control of the key presses and if it it not rendered, key presses are not handled correctly. This can be seen in the sandbox link, try doing the following to see the behavior:

  1. Click hello button to open menu
  2. Use the arrows to navigate the list, then hit Enter to select an option.
  3. Comment out line 20 (<div style={{ position: "absolute" }}>{props.children[0]}
) and repeat steps 1 and 2

You will see that when you comment out line 20, you lose the keyboard controls, so my question is, why does this happen, and what component is responsible for the focus management?

How can I achieve what I'm trying to do with react-select?

ebonow commented 3 years ago

@jbyomuhangi Fortunately, this is not an issue. Full explanation and working demo below

You will see that when you comment out line 20, you lose the keyboard controls, so my question is, why does this happen

First, an explanation of the impact of line 20

But, what about the button?

what component is responsible for the focus management

How can I achieve what I'm trying to do with react-select?

  • You simply need to maintain focus within the SelectContainer so that the keyDown listener will be able to control the Menu.
const Control = (props) => {
  const onClick = e => e.target.focus();

  return (
    <components.Control {...props}>
      <button onClick={onClick}> hello </button>
    </components.Control>
  );
};

Working demo

I hope this answers all of your questions. It wasn't entirely intuitive for me either, especially realizing that the button wasn't maintaining focus. I will close this issue as everything does appear to be functional, but happy to work through any other questions you have. As before, I can reopen this if necessary.

jbyomuhangi commented 3 years ago

Wow, thanks so much @ebonow! This works! Really appreciate the help and speedy replies

manish-zluri commented 11 months ago

I am facing issue 's where I've select component inside which we show input field in the option, when we try clicking the spacebar , arrow keys the it doesn't works, I guess this behaviour is being handled by the Select component. but it should be only for the input field used by Select and not to other components