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:

    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.