-
-
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; | |
} |
@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);
}
})
Thank you so Much for this, I will be implementing it right away and let you know if I face any issues😊.