0

I am having a dropdown list with certain years and showing the current year by default. I want to set an onBlur event to close the dropdown when the user clicks outside. onBlur works fine but my problem is I can't set the currently selected year. the problem here is that when I select a value from the dropdown which is the child of this div, it triggers the onBlur event hence dropdown closes and I can't set value. now, how do I achieve setting onBlur correctly so that selecting elements from the list doesn't trigger onBlur or is there any other way to achieve this.

this is Child component

import React from 'react';

const SelectProjection = (props) => {
  let years;
  if (props.yearList) {
    years = props.yearList.map((year, index) => {
      return (
        <div
          className="dropdown-child"
          title="value"
          key={index}
          role="presentation"
          onClick={(e) => props.filterHandler(year)}
          tabIndex={0}
        >
          <span id="dropdown-child-item">{year}</span>
        </div>
      );
    });
  } else {
    years = <div>Loading....</div>;
  }
  return (
    <div>
      <div
        id="sort-by"
        className={`${props.orderBy} ${
          props.open ? 'open' : ' close'
          }`}
      >
        <div className="header" style={{ fontSize: '12px' }}>
          Project to year
          </div>

        <div className="sort-by-dropdown" onBlur={props.hideDropdown} >
          <div
            className="selected"
            id="openProjectionFilter"
            onClick={props.showDrop}
            onKeyPress={props.showDrop}
            tabIndex={0}
            role="button"
          >
            <span id="selected-sort-item">{props.selectedYear} </span>
          </div>
          <div className="options">
            <div
              className="dropdown-child"
              title="value"
              role="presentation"
              tabIndex={1}
              onClick={() => props.filterHandler()}
            >
              <span id="dropdown-child-item">{props.selectedYear}</span>
            </div>

            {years}

          </div>
        </div>
      </div>
    </div>
  );
}


export default SelectProjection;

this is my parent component

 class HealthFilter extends React.Component {
    state = { openProjectionFilter: false };


    showProjectionList = () => {
        this.setState(state => ({ 
        openProjectionFilter:!state.openProjectionFilter }));
    };

    filterYearHandler = (year) => {
        console.log('child clicked')
        this.props.handlerYear(year)
        this.setState({ openProjectionFilter: false })
    }
    hideDropdown = (event) => {
    console.log('parent clicked')
    this.setState({ [event.target.children[0].id]: false })
    }


    render() {
    const { openProjectionFilter } = this.state;
    const { boms } = this.props

    return (
      <div id={'filter'}>
        <div id="alert-filter-body" className=''>         
          <SelectProjection
            open={openProjectionFilter}
            hideDropdown={this.hideDropdown}
            showDrop={this.showProjectionList}
            selectedYear={this.props.selectedYear}
            filterHandler={this.filterYearHandler}
            yearList={this.props.yearList}
          />            
          <div className="checkboxes" style={{ marginTop: 10 }}>
            <div className="filter-button green" role="button" onClick={this.props.submitProjections}>
              Calculate
            </div>
          </div>
        </div>
      </div>
    );
    }
    }
    HealthFilter.propTypes = {};

    export default HealthFilter;
Ashok
  • 976
  • 3
  • 15
  • 32
  • Have you tried lifting the `onBlur` to the parent `div` the one with `
    `
    – DANIEL SSEJJEMBA Nov 27 '19 at 10:51
  • No that didnt work. when i click the child, even though child has event handler it doesn't fire. on Blur fires. i am not sure what i am missing here@DANIELSSEJJEMBA – Ashok Nov 27 '19 at 12:24

3 Answers3

2

Since React v.17 the React onBlur / onFocus events use the native events focusin / focusout - which means, when using onBlur / onFocus with React 17+ those events will prpegate. (Which is pretty useful for dropdowns).

You can now just wrap input element and the dropdown element in one container div and set the focusevents on that container - and since focus events now propegate they will bubble from the input aswell as from the dropdown to the div (so clicking the dropdown will not trigger an onBlur event).

For a more indepth explenation see my answer here: https://stackoverflow.com/a/73922116/19529102 (It's a plain JS and HTML answer - no react - but that doen't really matter, since React already uses the propagating focus events (focusin and focusout)

Lord-JulianXLII
  • 1,031
  • 1
  • 6
  • 16
1

Use this dropdown component this work correctly, it detects the on blur and closes the dropdown.

import React, { useState } from "react";

const TableDropdown = () => {

  const [state, setState] = useState('hidden');

  const handleOpenDropdown = (event) => {
    setState('block');
  }

  const handleCloseDropdown = (event) => {
    if (!event.currentTarget.contains(event.relatedTarget)) {
      setState('hidden');  
    }
  }

  return (
    <React.Fragment>
      <div className="relative" onBlur={(event) => handleCloseDropdown(event)}>
        <div>
          <button type="button" onClick={(event) => handleOpenDropdown(event)} className="inline-flex w-fit justify-center itce rounded border bg-ascent text-white px-4 py-2 text-xs font-medium">Options</button>
        </div>
        <div className={`absolute ${state} border right-0 z-10 mt-2 w-fit origin-top-right rounded bg-slate-50 shadow-lg px-2 py-3`}>
          <ul className="flex flex-col space-y-3">
            <li><a href="#" onClick={() => console.log('Clicked on link')} className="text-xs font-medium text-slate-700 hover:text-ascent px-3 py-2 whitespace-nowrap">Account Information</a></li>
            <li><a href="#" className="text-xs font-medium text-slate-700 hover:text-ascent px-3 py-2 whitespace-nowrap">Account Information</a></li>
            <li><a href="#" className="text-xs font-medium text-slate-700 hover:text-ascent px-3 py-2 whitespace-nowrap">Account Information</a></li>
            <li><a href="#" className="text-xs font-medium text-slate-700 hover:text-ascent px-3 py-2 whitespace-nowrap">Account Information</a></li>
          </ul>
        </div>
      </div>
    </React.Fragment>
  );
}

export default TableDropdown;
0

because class .options is not in class .selected. the solution is you move onBlur={props.hideDropdown} to the parent of both. <div className="sort-by-dropdown" onBlur={props.hideDropdown}>

Oang Phạm
  • 127
  • 1
  • 7
  • @Ashok try to add ```event.preventDefault()``` in the function ```filterHandler()``` : – Oang Phạm Nov 27 '19 at 14:42
  • I have tried that but that didn't work. I have edited the question and added the parent component as well. I am not sure whats mistake. I replicated the same scenario in https://codesandbox.io/s/react-example-u3g50 and it works fine but not here. i am confused @Oang Pham – Ashok Nov 28 '19 at 04:58