4

I have some components in my app which are expected to handle some user inputs from the keyboard. For that I created the following function:

export default function withKeydownEventHandler (handler) {
  id = id + 1
  return lifecycle({
    componentWillMount () {
      $(window).on(`keydown.${id}`, evt => handler(evt))
    },
    componentWillUnmount () {
      $(window).off(`keydown.${id}`)
    }
  })
}

This works fine, but the handlers are being fired off for different components at the same time. So if my handler does different things in each component, whenever I click a button it will be fired off from both components at the same time. Also, once one component is unmounted, the HoC will no longer work.

For example, say I have the following two containers:

export default compose(
  withKeydownEventHandler((evt, props) => {
    console.warn('hi from Component 1')
  }),
  withProps(() => {
    // stuff
  })
)(Component1)

export default compose(
  withKeydownEventHandler((evt, props) => {
    console.warn('hi from Component 2')
  }),
  withProps(() => {
    // stuff
  })
)(Component2)

If I click any button throughout the app, I will get the following output:

hi from Component 1

hi from Component 2

On the flip side, once one of the components becomes unmounted, I no longer get any events.

What am I doing wrong? How can I get a keydown event handler through an HoC that can be re-used throughout my app?

Community
  • 1
  • 1
theJuls
  • 6,788
  • 14
  • 73
  • 160

1 Answers1

3

Firstly, may I bring to your attention that your id is set as a global variable. Are you sure you want to have such a variable name as a global?

Secondly, you are binding the keydown event to the windows with $(window).on('keydown.${id}', evt => handler(evt)) which explains your unwanted behaviour. You need to bind it once with the specific component you want the handler to act to.

Finally, why dont you create a HOC class and add the event listeners conditionally? like the following:

// src/Hoc.jsx

export default function(WrapperComponent) {
  return class extends Component {
    componentWillMount () {
      const { onKeyDownHandler } = this.props;
      if (isKeyDownEventNeeded) {
         this.comp.addEventListener("keydown", onKeyDownHandler);
      }
    }
    componentWillUnmount () {
      const { onKeyDownHandler } = this.props;
      if (isKeyDownEventNeeded) {
         this.comp.removeEventListener("keydown", onKeyDownHandler);
      }
    }
    render() {
        const { onKeyDownHandler } = this.props;
        if (onKeyDownHandler) { 
            // a "ref" callback which assigns the mounted 
            // Element to a prop "comp" whicu can be used later to add the DOM listener to.
            return <WrapperComponent ref={elem => this.comp = elem} {...this.props} />
        }
        return <WrapperComponent {...this.props} />
    }
}

export default HighOrderComponent;

Then

// somewhere-else.js

import highOrderComponent from 'src/Hoc'    
highOrderComponent(<Component1 onKeyDownHandler={() => console.log('hey, Component 1'} />
highOrderComponent(<Component2 onKeyDownHandler={() => console.log('hey, Component 2'} />

For more info on how check out this answer

Matthew Barbara
  • 3,792
  • 2
  • 21
  • 32