Bootstrap Dropdown update position after state change

Viewed 697

I'm using a search input box inside bootstrap dropdown. When I search any value it filters the dropdown to show only filtered dropdown items.

The problem here is when the dropdown position is up and I filter the list, The starting point of dropdown items remains same leaving gap between the dropdown items and the dropdown. But works properly when dropdown is open at bottom position.

When i click on the dropdown button, Bootstrap adds x-placement="top-start" like below and it never updates when i filter the value. I want to update the position so that everytime it starts from dropdown and there is no gap between.

<div class="dropdown show">
<div class="dropdown-menu show" 
x-placement="top-start" 
style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, -279px, 0px);">
...
</div>
</div>

I have added the codesandbox of the problem

https://stackblitz.com/edit/react-xz1jvq?file=src/App.js

Dropdown after filter leaves space in between. Basically i want to update its position everytime after filter

enter image description here

1 Answers

Definitely not the best solution but I think you can work with this

Key parts: I removed all the bootstrap code for opening modals and changed the click function of the toggle button.

Then, inside componentDidUpdate, using selectors and vanilla js I forced the dropdown menu to be hidden and shown again so as measurements to be computed again.

import React from 'react';
import './style.css';
import ReactDOM from 'react-dom';
import $ from 'jquery';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isDropdownOpen: false,
      inputValue: '',
      options: ['action', 'newAction', 'oldAction']
    };
  }

  componentDidUpdate(prevProps, prevState) {
    if (
      $('.dropdown-menu')
        .attr('class')
        .indexOf('show') >= 0
    ) {
      $('#dropdownMenuButton').trigger('click.bs.dropdown');
      $('#dropdownMenuButton').trigger('click.bs.dropdown');
    } else {
      $('#dropdownMenuButton').trigger('click.bs.dropdown');
    }

    $('#search').focus();
  }

  handleChange = evt => {
    const value = evt.target.value;
    this.setState({ inputValue: value });
  };
  render() {
    return (
      <div>
        <h1>Hello StackBlitz!</h1>
        <h1>Hello StackBlitz!</h1>
        <h1>Hello StackBlitz!</h1>
        <h1>Hello StackBlitz!</h1>
        <h1>Hello StackBlitz!</h1>
        <h1>Hello StackBlitz!</h1>
        <div className="dropdown" ref={node => (this.DDRef = node)}>
          <button
            className="btn btn-secondary dropdown-toggle"
            type="button"
            id="dropdownMenuButton"
            data-toggle="dropdown"
            aria-haspopup="true"
            aria-expanded="false"
            onClick={() =>
              this.setState({ isDropdownOpen: !this.state.isDropdownOpen })
            }
          >
            {this.state.isDropdownOpen ? (
              <div style={{ display: 'flex' }}>
                <input
                  id="search"
                  type="text"
                  defaultValue={''}
                  value={this.state.inputValue}
                  placeholder={'Search'}
                  ref={node => (this.inputRef = node)}
                  onChange={this.handleChange}
                />
                <span
                  className="cancel-icon"
                  ref={node => (this.cancelRef = node)}
                >
                  x
                </span>
              </div>
            ) : (
              <div>Dropdown button</div>
            )}
          </button>
          <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
            {this.state.options
              .filter(el => el.startsWith(this.state.inputValue))
              .map(option => {
                return (
                  <a className="dropdown-item" href="#">
                    {option}
                  </a>
                );
              })}
          </div>
        </div>
      </div>
    );
  }
}

export default App;
Related