I want to be sure that all my onClick event are next to a onKeyDown event.
I will use eslint-plugin-jsx-a11y to ensure this. But in code, It is a way to do this generic. I mean, it will be annoying to do all the time:
if(event.keyCode === 13){ ... }I would like to have a way to tell an element that in onKeyDown in case that the user use the execute the function in the onClick. Or a similar solution like
In angular for instance, I have this clear. Let's go for a directive to do this automatically. But in React I don't know which is the best approach.
eslint rule:
35 Answers
Another approach without decorators could be to use object spread to add the necessary props to an element:
function buttonize(handlerFn) { return { role: 'button', onClick: handlerFn, onKeyDown: event => { // insert your preferred method for detecting the keypress if (event.keycode === 13) handlerFn(event); } }
}and use like so:
<div className="element-with-very-good-excuse-to-dont-be-a-button" {...buttonize(this.myAction)}
>click me</div> 0 Finally I only see 2 solutions:
1 I can create a component that encapsulate all those actions... But this is the work of a button. And I want not to open a new way to create buttons in my project, this is just for exceptions and this can create a vicious behave inside the project. And for that we have another component... the button :)
2 create a decorator for the actions.
export function a11yButtonActionHandler(target, key, descriptor) { const fn = descriptor.value; if (typeof fn !== 'function') { throw new Error(`@a11yButtonActionHandler decorator can only be applied to methods not: ${typeof fn}`); } descriptor.value = function actionHandler(event) { if (!event || event.type === 'click' || (['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key)) ) { fn.call(this, event); } }; return descriptor;
}and use the decorator.
@a11yButtonActionHandler
myAction(event) { ...
}<div className="element-with-very-good-excuse-to-dont-be-a-button" tabIndex="0" onKeyDown={ this.myAction } onClick={ this.myAction }> Great solutions here already, but I wanted an option where I didn't have to use decorators or prop-spreading. So I rejigged some of the existing answers into this:
function keyDownA11y(handler) { return function onKeyDown(event) { if ( ['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key) ) { handler(); } }
}usage:
<div onClick={myHandler} onKeyDown={keyDownA11y(myHandler)}
> Hurray!
</div>EDIT: Alternatively, you could use this to make an A11yButton component and just import that. This way, you could get the benefits of cragb's excellent buttonizer solution:
import React from 'react';
export default function A11yButton({ elementType, onClick, ...props
}) { return React.createElement(elementType, { ...props, onClick, onKeyDown: keyDownA11y(onClick), role: 'button', // Add other props that might be necessary, like "tabIndex: 0," });
}Usage:
<A11yButton elementType="div" onClick={myHandler}
> Hurray!
</A11yButton> 1 To simplify Raúl Martíns answer and don't use decorators which are still just a proposal you can also create a simple function out of it:
function isButtonAction(event) { return ( event.type === 'click' || (['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key)) );
}then simply check it in your action handler:
function myAction(event) { if (isButtonAction(event)) { ... }
} I suggest you use a state to handle it. For example:
First, initial a state in your component and set the event listener:
getInitialState(){ window.addEventListener("keypress", this.onKeypresshandler);// if you are not rendering on server side, you cannot bind the listener to the element you want, which doesn't exist yet. return{ isPressed: false }
}Then, change the state when the key keypress event is called:
onKeypresskhandler(e){ if(e.keyCode === 13){// if the key is enter this.setState({isPressed:true}) }
}finally, check the state before executing your function
YourFunction(){ if(this.state.isPressed){// check if the key is pressed ... // the code you want to execute }
}It is the simplest way I know. I hope it helps.
1