Search code examples
javascriptcoffeescriptreactjsrefluxjs

I want to make a dropdown in React. Clicking a button should open a submenu. Clicking outside of the submenu should close it


Here is my existing working code. This works fine apart from the fact that if I click outside of the submenu, when it's open, the sub-menu remains open.

class UserTypeDropdown extends React.Component
  constructor: (props) ->
    super(props)
    @state =
      display_submenu: false
  @propTypes:
    selected: React.PropTypes.string.isRequired

  submenu_display:->
    if @state.display_submenu
      'block'
    else
      'none'
  button_class_names:->
    classNames(
        'select-btn ': true,
        'user_role ': @props.selected is 'user',
        'admin_role ': @props.selected is 'admin'
        )

  selected_text:->
    if @props.selected == 'user'
      'User'
    else
      'Administrator'

  render:->
    div {className: 'user-type-dropdown'},
      button { className: @button_class_names(), onClick: @_toggleSubmenu },
        @selected_text()
      ul {className: 'submenu submenu_content access_submenu', style: {display: @submenu_display()}},
        li {onClick: @select_admin},
          a {},
            'Administrator'
        li {onClick: @select_user},
          a {},
            'User'
  _toggleSubmenu: =>
    @setState display_submenu: !@state.display_submenu
  select_admin: =>
    @_toggleSubmenu()
    actions.setUserRole('admin')
  select_user: =>
    @_toggleSubmenu()
    actions.setUserRole('user')

I want to implement that clicking outside of the submenu when it's open, closes it.

I've read into how to do this in React and I've come across a lot of suggestions that say I should use onFocus and onBlur instead of onClick

This makes sense to me as it means the drop-down should only be open when it's in focus in the browser. So my render function & methods become.

  render:->
    div {className: 'user-type-dropdown'},
      button { className: @button_class_names(), onFocus: @_toggleSubmenu, onBlur: @_toggleSubmenu },
        @selected_text()
      ul {className: 'submenu submenu_content access_submenu', style: {display: @submenu_display()}},
        li {onClick: @select_admin},
          a {},
            'Administrator'
        li {onClick: @select_user},
          a {},
            'User'
  _toggleSubmenu: =>
    @setState display_submenu: !@state.display_submenu
  select_admin: =>
    actions.setUserRole('admin')
  select_user: =>
    actions.setUserRole('user')

However with onFocus: and onBlur: on the button element, Now clicking on either of the li's wont trigger the @select_admin or @select_user it just triggers @_toggle_submenu and the click doesn't seem to propigate / bubble down to the li element underneath..

From reading the React Event Documentation and This Focus and hotkeys guide I can see that the events should bubble down but for me they simply aren't.

I'm new to the React Coffescript/JS world so forgive any incorrect naming conventions.

I think my understanding must be flawed because I can't find an answer anywhere to this problem.

Any help would be greatly appreciated.


Solution

  • Your issue is: as soon as you click an item, the onBlur event fires first. This causes a re-render (because you do setState). And therefore your onClick event is never run.

    The way to solve this (with onFocus and onBlur), is to rearrange your HTML setup:

    • make sure your main menu item can have focus (<button> is already OK)
    • make sure your submenu can NOT have focus (<li> is OK)
    • make sure you submenu is INSIDE your main menu component (you would need to change this)

    That way your Main menu item does not lose focus if you click one of the sub-items.

    You can find a simple (HTML and javascript only) JSBIN example here.