Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Input bug: cannot type 'S' into search input

I almost give up on this bug. I simply just can't type "S" into the search input.

The keyboard works fine.

Sandbox below.

https://codesandbox.io/s/jolly-raman-61zbx?file=/src/App.js

Code from sandbox:

import {
  Box,
  FormControl,
  InputAdornment,
  ListItem,
  Menu,
  TextField,
  withStyles
} from "@material-ui/core";
import {
  clamp,
  difference,
  includes,
  intersection,
  join,
  map,
  union
} from "lodash";

import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
import { FixedSizeList } from "react-window";
import Fuse from "fuse.js";
import React from "react";
import SearchIcon from "@material-ui/icons/Search";
import memoize from "memoize-one";

const styles = {
  popoverPaper: {
    width: "100%"
  }
};

const fuseOptions = {
  includeScore: true
};

const initialSearchState = {
  searchValue: ""
};

const getCheckedList = (list) => (!!list ? list : []);

class VirtualisedSelector extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      anchorEl: null,
      ...initialSearchState
    };
  }

  getSearchList = memoize((list, searchValue) => {
    const fuse = new Fuse(list, fuseOptions);
    return !!searchValue ? map(fuse.search(searchValue), (o) => o.item) : list;
  });

  handleSearch = (event) => {
    this.setState({
      ...this.state,
      searchValue: event.target.value
    });
  };

  getCleanSelectedValues = memoize((currentSelectedValues, list) =>
    intersection(currentSelectedValues, list)
  );

  getNewMultipleValue = (currentSelectedValues, clickedItemValue) =>
    includes(currentSelectedValues, clickedItemValue)
      ? difference(currentSelectedValues, [clickedItemValue])
      : union(currentSelectedValues, [clickedItemValue]);

  getNewSingleValue = (currentSelectedValues, clickedItemValue) =>
    includes(currentSelectedValues, clickedItemValue) ? [] : [clickedItemValue];

  getNewValue = (currentSelectedValues, clickedItemValue, list, multiple) =>
    multiple
      ? this.getNewMultipleValue(
          this.getCleanSelectedValues(currentSelectedValues, list),
          clickedItemValue
        )
      : this.getNewSingleValue(currentSelectedValues, clickedItemValue);

  handleChange = (currentSelectedValues, list, multiple) => (event) => {
    this.setState({
      anchorEl: null
    });
    const clickedItemValue = event.target.getAttribute("value");
    const newValue = this.getNewValue(
      currentSelectedValues,
      clickedItemValue,
      list,
      multiple
    );
    this.props.onSelect(newValue);
  };

  getTextFieldDisplayValue = (value, list, labelMap) =>
    join(
      map(this.getCleanSelectedValues(value, list), (e) => labelMap[e] ?? e),
      ", "
    );

  handleMenuOpen = (event) => {
    this.setState({ anchorEl: event.currentTarget });
  };

  handleMenuClose = (event) => {
    this.setState({ anchorEl: null, ...initialSearchState });
  };

  renderRow = () => ({ index, style }) => {
    const { multiple, value, list, labelMap } = this.props;
    const { searchValue } = this.state;
    const searchList = this.getSearchList(list, searchValue);

    const cleanSelectedValues = this.getCleanSelectedValues(value, list);
    const listItem = searchList[index];
    return (
      <ListItem
        value={listItem}
        key={listItem}
        selected={includes(value, listItem)}
        onClick={this.handleChange(cleanSelectedValues, list, multiple)}
        style={style}
      >
        {labelMap?.[listItem] || listItem}
      </ListItem>
    );
  };

  render() {
    const {
      value,
      list,
      name,
      label,
      labelMap,
      required = false,
      classes
    } = this.props;
    const { searchValue } = this.state;
    const formControlClassNames = this.props?.classes?.formControl;

    const searchList = this.getSearchList(getCheckedList(list), searchValue);
    const nItems = searchList.length;
    const stringHeight = 46;
    const fixedSizeListHeight = stringHeight * clamp(nItems, 1, 10);

    return (
      <FormControl className={formControlClassNames} fullWidth>
        <TextField
          onClick={this.handleMenuOpen}
          variant="outlined"
          required={required}
          InputProps={{
            endAdornment: (
              <InputAdornment position="end">
                <ArrowDropDownIcon />
              </InputAdornment>
            )
          }}
          value={this.getTextFieldDisplayValue(value, list, labelMap)}
          multiline
          type="text"
          name={name}
          label={label}
          fullWidth
        />
        <Menu
          anchorEl={this.state.anchorEl}
          keepMounted
          open={Boolean(this.state.anchorEl)}
          onClose={this.handleMenuClose}
          PopoverClasses={{ paper: classes.popoverPaper }}
        >
          {/* Search */}
          <Box p={1}>
            <TextField
              variant="outlined"
              InputProps={{
                startAdornment: (
                  <InputAdornment position="start">
                    <SearchIcon />
                  </InputAdornment>
                )
              }}
              type="text"
              label={"Search"}
              value={searchValue}
              onChange={this.handleSearch}
              fullWidth
            />
          </Box>

          {/* Virtualized list */}
          <FixedSizeList
            height={fixedSizeListHeight}
            width={"100%"}
            itemSize={stringHeight}
            itemCount={nItems}
          >
            {this.renderRow()}
          </FixedSizeList>
        </Menu>
      </FormControl>
    );
  }
}

VirtualisedSelector.defaultProps = {
  value: "",
  list: [],
  name: "",
  label: "",
  labelMap: {},
  required: false
};

export default withStyles(styles)(VirtualisedSelector);
like image 745
kkkkkkkkkkkkkkkkk Avatar asked Apr 24 '26 16:04

kkkkkkkkkkkkkkkkk


1 Answers

The main issue is that you shouldn't be using Menu for this. Menu assumes that it has MenuItem children and has accessibility functionality geared towards that assumption. The behavior you are seeing is caused by the functionality that tries to navigate to menu items by typing the character that the menu item's text starts with. In your case, it is finding the text of the label "Search", and then it is moving focus to that "menu item" (which is why you then get a focus outline on the div containing your TextField). If you change the label to "Type Here", you'll find the "s" works, but "t" doesn't.

My recommendation would be to use Popover directly (the lower-level component which Menu delegates to for the main functionality you are using from it). Another option would be to use the Autocomplete component since you seem to be trying to use Menu and the pop-up TextField to do your own custom version of what the Autocomplete component provides.

like image 134
Ryan Cogswell Avatar answered Apr 27 '26 05:04

Ryan Cogswell



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!