-
-
Save gragland/b61b8f46114edbcf2a9e4bd5eb9f47f5 to your computer and use it in GitHub Desktop.
import { useState, useEffect } from 'react'; | |
// Usage | |
function App() { | |
// Call our hook for each key that we'd like to monitor | |
const happyPress = useKeyPress('h'); | |
const sadPress = useKeyPress('s'); | |
const robotPress = useKeyPress('r'); | |
const foxPress = useKeyPress('f'); | |
return ( | |
<div> | |
<div>h, s, r, f</div> | |
<div> | |
{happyPress && '😊'} | |
{sadPress && '😢'} | |
{robotPress && '🤖'} | |
{foxPress && '🦊'} | |
</div> | |
</div> | |
); | |
} | |
// Hook | |
function useKeyPress(targetKey) { | |
// State for keeping track of whether key is pressed | |
const [keyPressed, setKeyPressed] = useState(false); | |
// If pressed key is our target key then set to true | |
function downHandler({ key }) { | |
if (key === targetKey) { | |
setKeyPressed(true); | |
} | |
} | |
// If released key is our target key then set to false | |
const upHandler = ({ key }) => { | |
if (key === targetKey) { | |
setKeyPressed(false); | |
} | |
}; | |
// Add event listeners | |
useEffect(() => { | |
window.addEventListener('keydown', downHandler); | |
window.addEventListener('keyup', upHandler); | |
// Remove event listeners on cleanup | |
return () => { | |
window.removeEventListener('keydown', downHandler); | |
window.removeEventListener('keyup', upHandler); | |
}; | |
}, []); // Empty array ensures that effect is only run on mount and unmount | |
return keyPressed; | |
} |
Thank you so Much for this, I will be implementing it right away and let you know if I face any issues😊.
@felipe-dap
Hey, I tried your solution but it's a little more complicated for me and my code just became more complex. Can you help me out with this, I am struggling pretty hard to implement this.
This is My Component
Here is my component (I am using Material UI v5) 👇🏻
`import * as PropTypes from 'prop-types';
import styled from '@emotion/styled';
import { css } from '@emotion/react';
import { Grid } from '@mui/material';
import useTraversalThroughInputs from './useTraversalThroughInputs';
import { useEffect } from 'react';
const styles = csswidth: 100%; padding: 0 10px; border: 1px solid black; font-size: 14px; font-weight: bold; margin: 5px 0 0 0; height: 25px; border-radius: 2px;
;
const titleStyles = csswidth: 20%; height: 5vh; text-align: center; padding: 5px 25px; font-style: italic; font-size: 1rem; color: #fff;
;
const StyledOpen = styled.td${titleStyles}; background-color: #ce4848;
;
const StyledJodi = styled.td${titleStyles}; color: #000; background-color: #37e180;
;
const StyledClose = styled.td${titleStyles}; background-color: #000;
;
const StyledInput = styled.input${styles}
;
const StyledOpenInput = styled.inputbackground-color: #ce4848; ${styles}
;
const StyledCloseInput = styled.inputcolor: #fff; background-color: #000; ${styles}
;
function Jodi({ jodiArr = [] }) {
// eslint-disable-next-line prefer-const
let active = [1, 1]; // row and column, so it would point to 1C as active.
const current = useTraversalThroughInputs(active);
const jodi = [...jodiArr];
const row = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const column = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
useEffect(() => {
// select the input element. depends on the structure of your table.
const tableBody = document.querySelector('#table-body ');
tableBody.focus();
// useEffect you be triggered every time active has been changed, and will change focus to your new active cell.
}, [active]);
const openClose = jodi.slice(0, 10);
console.log(jodi);
return (
<>
<Grid container spacing={1} component="table" direction="column" justifyContent="space-between">
<thead>
<Grid item component="tr" sx={{ display: 'flex', justifyContent: 'space-between' }}>
<StyledOpen>Open</StyledOpen>
<StyledJodi>Jodi</StyledJodi>
<StyledClose>Close</StyledClose>
</Grid>
</thead>
<tbody id="table-body">
{openClose.map((open, index) => {
const jodis = jodi.splice(0, 10);
// eslint-disable-next-line prefer-const
let indexJodi = index * 10;
return (
<tr key={open} id={row[index]}>
<Grid item xs={1} md={1} xl={1} component="td">
<label htmlFor={`OPEN_${open}`}>{open}</label>
<StyledOpenInput type="text" id={column[index]} name={`OPEN_${open}`} />
</Grid>
{jodis.map((jodi, indexJ) => (
<Grid item key={indexJodi + indexJ} xs={1} md={1} xl={1} component="td">
<label htmlFor={`Jodi_${jodi}`}>{jodi}</label>
<StyledInput type="text" id={column[index]} name={`Jodi_${jodi}`} />
</Grid>
))}
<Grid item key={open + 2} xs={1} md={1} xl={1} component="td">
<label htmlFor={`CLOSE_${open}`}>{open}</label>
<StyledCloseInput type="text" id={column[index]} name={`CLOSE_${open}`} />
</Grid>
</tr>
);
})}
</tbody>
</Grid>
</>
);
}
Jodi.propTypes = {
jodiArr: PropTypes.array
};
export default Jodi;
`
Here is My Array that I am passing to this component👇🏻
`const jodiArr = [
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99"];
export default jodiArr;
`
@Suryakaran1234
Sure I can help you out on this. =)
Do you mind if I make a few refactorings along the way? Mostly for organization pourposes...
This is getting way off topic, so I made a repo in order for not cluttering this thread anymore.
Probably tomorrow there should be a demo on there. Find me there.
https://github.com/felipe-dap/useTraversalThroughInputs
and we can chat at
felipe-dap/useTraversalThroughInputs#1
Cheers.
@felipe-dap
Yeah of course go ahead, it will be helpful for me if you do refactorings, I will get to learn more.
function testUseKeyPress() { const onPressSingle = () => { console.log('onPressSingle!') } const onPressMulti = () => { console.log('onPressMulti!') } useKeyPress('a', onPressSingle) useKeyPress('shift h', onPressMulti) }
onKeyPressed
Thanks @jeremytenjo
For folks attempting to capture
Meta
key pressed in combination with other keys (Command+k
e.g. on Macs), be warned that keyup events do not fire when theMeta
key is still pressed. That means that this hook cannot be used reliably to detect when keys are unpressed. Read issue #3 here: https://web.archive.org/web/20160304022453/http://bitspushedaround.com/on-a-few-things-you-may-not-know-about-the-hellish-command-key-and-javascript-events/To work around this, I do not rely on keyup events at all but instead "unpress" automatically after a second. It's a bit hacky but serves my use case quite well (Command+k):
export function useKeyPress(targetKey: string) { // State for keeping track of whether key is pressed const [keyPressed, setKeyPressed] = useState<boolean>(false); // Add event listeners useEffect(() => { // If pressed key is our target key then set to true function downHandler({ key }: any) { if (!keyPressed && key === targetKey) { setKeyPressed(true); // rather than rely on keyup to unpress, use a timeout to workaround the fact that // keyup events are unreliable when the meta key is down. See Issue #3: // http://web.archive.org/web/20160304022453/http://bitspushedaround.com/on-a-few-things-you-may-not-know-about-the-hellish-command-key-and-javascript-events/ setTimeout(() => { setKeyPressed(false); }, 1000); } } window.addEventListener("keydown", downHandler); // Remove event listeners on cleanup return () => { window.removeEventListener("keydown", downHandler); }; }, []); // Empty array ensures that effect is only run on mount and unmount return keyPressed; }And if anyone is interested, here's my tiny
useKeyCombo
extension that can be used likeconst isComboPress = useKeyCombo("Meta+k");
to captureCommand+k
:export const useKeyCombo = (keyCombo: string) => { const keys = keyCombo.split("+"); const keyPresses = keys.map((key) => useKeyPress(key)); return keyPresses.every(keyPressed => keyPressed === true); };Lastly, I almost always just want to trigger some logic when these key conditions are met, so I made a wrapper hook that does that for me and allows for usage like this:
useOnKeyPressed("Meta+k", () => setIsQuickSearchOpen(true));
:export const useOnKeyPressed = (keyCombo: string, onKeyPressed: () => void) => { const isKeyComboPressed = useKeyCombo(keyCombo); useEffect(() => { if (isKeyComboPressed) { onKeyPressed(); } }, [isKeyComboPressed]); };
have encountourred any problem while using this?
I would suggest adding 'blur' event to the window. I'm using this hook to see if 'Shift' is being pressed, but when I pressed 'Shift' and at same time go to another window (e.g devtools or another tab), the state wasn't being set as false, and when I went back to my tab the state was still true (since I release 'Shift' in another tab).
// ...
const setAsNotBeingPressed = useCallback(() => {
setKeyPressed(false);
}, []);
const setAsBeingPressed = useCallback(() => {
setKeyPressed(true);
}, []);
useEffet(() => {
// ....
window.addEventListener("blur", setAsBeingPressed);
return () => {
// ...
window.removeEventListener("blur", setAsNotBeingPressed);
}
})
@Suryakaran1234
Cool. This seems like a nice challenge.
What I would try is something like this:
37 --> left
38 --> up
39 --> right
40 --> down
So our hook function needs to detect a keypress, check if the keyCode is any of these values from 37 to 40 and call the event.
Now our hook gives us the current cell position. We could go further and incorporate into the hook the ability to select the element and use input.focus() to make it active. But I will keep concerns separate here for clarity.