Add event listener to every Component with a given property

Viewed 27

I have a lot of Components with onKeyup event listeners, which then check to see if the key was Tab and if so, take an action. I'd like to abstract that a bit and create an onTab property, where the value is a callback function that executes that action. But, importantly, I'd like the "was this key a Tab?" logic to be abstracted away. My thought was that I could write logic that said "When initializing this Component, check if it has an onTab property, and if so, create an onKeyup event listener with X logic to trigger the onTab callback." However, I'm unsure how to do that.

Can anyone help?

1 Answers

I can think of 3 solutions, each with its advantages and complexity.

First, use hook:

// use-on-tab.js

const useOnTab = function (callback) {
  let listener = function (event) {
    if (event.code === 'Enter') {
      callback()
    }
  }

  return listener
}

export default useOnTab
// Foo.js

import useOnTab from './use-on-tab'

const Foo = function () {
  let onTab = useOnTab(() => console.log('foo'))
  return <div onKeyUp={onTab} tabIndex="0">Foo</div>
}

export default Foo

This solution is quite simple, you can hide the logic of checking key and only pass the callback. But you still have to manually call useOnTab and add onKeyUp prop on every components.


Second, use HOC:

// with-on-tab.js

const withOnTab = function (WrappedComponent) {
  let createListener = function (callback) {
    return function (event) {
      if (event.code === 'Enter') {
        callback()
      }
    }
  }

  return function ({ onTab, ...props }) {
    return <WrappedComponent {...props} onKeyUp={createListener(onTab)} />
  }
}

export default withOnTab
// Bar.js

import withOnTab from './with-on-tab'

const Bar = function (props) {
  return <div tabIndex="0" {...props}>Bar</div>
}

export default withOnTab(Bar)
<Bar onTab={() => console.log('bar')} />

Read more about Higher-Order Components.


And the last one is custom JSX:

// jsx-dev-runtime.js

import * as ReactJSXRuntimeDev from 'react/jsx-dev-runtime'

export const Fragment = ReactJSXRuntimeDev.Fragment

export function jsxDEV(
  type,
  props,
  key,
  isStaticChildren,
  source,
  self
) {
  let newProps = props

  if (Object.prototype.hasOwnProperty.call(props, 'onTab')) {
    let { onTab, ...props } = newProps

    newProps = {
      ...props,
      onKeyUp: event => {
        if (event.code === 'Enter') {
          onTab()
        }
      }
    }
  }

  return ReactJSXRuntimeDev.jsxDEV(
    type,
    newProps,
    key,
    isStaticChildren,
    source,
    self
  )
}
// Baz.js

const Baz = function (props) {
  return <div tabIndex="0" {...props}>Baz</div>
}

export default Baz
/** @jsxImportSource custom-jsx-library */

<Baz onTab={() => console.log('baz')} />

I get this idea from @emotion/react which has to deal with the css prop. Github

Related