Reputation: 79
I have the following setup:
const templates = [
'dot',
'line',
'circle',
'square',
];
const [currentTemplate, setCurrentTemplate] = useState(0);
const changeTemplate = (forward = true) => {
let index = currentTemplate;
index = forward ? index + 1 : index - 1;
if (index > templates.length - 1) {
index = 0;
} else if (index < 0) {
index = templates.length - 1;
}
setCurrentTemplate(index);
};
useEffect(() => {
console.log(`Current Template is: ${templates[currentTemplate]}`);
}, [currentTemplate]);
useKeypress('ArrowLeft', () => {
changeTemplate(false);
});
useKeypress('ArrowRight', () => {
changeTemplate(true);
});
This is the useKeypress-Hook is used:
import { useEffect } from 'react';
/**
* useKeyPress
* @param {string} key - the name of the key to respond to, compared against event.key
* @param {function} action - the action to perform on key press
*/
export default function useKeypress(key: string, action: () => void) {
useEffect(() => {
function onKeyup(e: KeyboardEvent) {
if (e.key === key) action();
}
window.addEventListener('keyup', onKeyup);
return () => window.removeEventListener('keyup', onKeyup);
}, []);
}
Whenever I press the left or right arrow key, the function gets triggered. But the currentTemplate
variable is not changing. It always stays at 0. The useEffect
is only triggered when I swich the key from left to right or the other way. Clicking the same key twice, does not trigger useEffect
again, but it should! When changing from right to left, the output is Current Template is: square
and when changing from left to right, it is Current Template is: line
. But this never changes. The value of currentTemplate
always stays 0
.
What am I missing?
Upvotes: 0
Views: 112
Reputation: 6205
The issue is with your useEffect
inside the useKeypress
hook.
export default function useKeypress(key: string, action: () => void) {
useEffect(() => {
function onKeyup(e: KeyboardEvent) {
if (e.key === key) action();
}
window.addEventListener('keyup', onKeyup);
return () => window.removeEventListener('keyup', onKeyup);
}, []); // 👈 problem
}
The hook is a single useEffect
with no dependencies. Meaning, it will only fire once. This is a problem for the action
you are passing into this hook. Whenever the changeTemplate
-action changes in your component (i.e. on a rerender), the reference to this is not renewed inside of the useKeypress
-hook.
To solve this, you need to add the action
to the dependency array of the useEffect
:
export default function useKeypress(key: string, action: () => void) {
useEffect(() => {
function onKeyup(e: KeyboardEvent) {
if (e.key === key) action();
}
window.addEventListener('keyup', onKeyup);
return () => window.removeEventListener('keyup', onKeyup);
}, [action]); // 👈 whenever `action` changes, the effect will be updated
}
This is to ensure that the effect is updated whenever the given action
is changed. After this change, things should be working as expected.
The useEffect
will at this point be regenerated for each render, as the changeTemplate
function will be instanciated for each render. To avoid this, you can wrap the changeTemplate
with a useCallback
, so that the function is memoized:
const changeTemplate = useCallback(
(forward = true) => {
let index = currentTemplate;
index = forward ? index + 1 : index - 1;
if (index > templates.length - 1) {
index = 0;
} else if (index < 0) {
index = templates.length - 1;
}
setCurrentTemplate(index);
},
// Regenerate the callback whenever `currentTemplate` or `setCurrentTemplate` changes
[currentTemplate, setCurrentTemplate]
);
Upvotes: 1