Reputation: 7167
I have a react component that has multiple choices. I've associated to each choice a shortcut. For that I'm using "react-hotkeys-hook".
So answer first answer has shortcut 1, second answer has answer 2 and so on.
The thing is, after answering, I update the view to show another question that can have a different number of choices. This makes the hooks blow up because the number of hooks varied from render to render.
// sort of pseudo-code
const comp = () => {
for(let i=1; i < 6; i++) {
useHotKey(i, callback, [dependencies])
}
return (
<>
<choice/>
<choice/>
</>
)
}
How would I go about implementing this? Currently I just create 5 shortcuts by default, but I'd like the number of keyboard shortcuts to not be hard-coded.
Upvotes: 1
Views: 1000
Reputation: 39270
Maybe the following example can help you out:
const useHotKey = (keys, callback) => {
React.useEffect(() => {
const keyUp = (e) => {
if (keys.includes(e.key)) {
callback(e.key);
}
};
document.body.addEventListener('keyup', keyUp);
return () =>
document.body.removeEventListener('keyup', keyUp);
}, [callback, keys]); //callback never chanes but keys does
};
const questions = [
['1', '2', '3', '4'],
['1', '2'],
['1', '2', '3'],
['a', 'b', 'c'],
];
const Answer = ({ answers, answer }) => {
useHotKey(answers, answer);
return <pre>ansers:{JSON.stringify(answers)}</pre>;
};
const App = () => {
const [current, setCurrent] = React.useState(0);
const [answers, setAnswers] = React.useState([]);
//answer is the callback but never changes because
// there are no dependencies
const answer = React.useCallback((answer) => {
setAnswers((answers) => [...answers, answer]);
setCurrent((current) => current + 1);
}, []); //no dependencies, answers only created on mount
return current < questions.length ? (
<Answer answers={questions[current]} answer={answer} />
) : (
<pre>
answers given:{JSON.stringify(answers, undefined, 2)}
</pre>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 3
Reputation: 53894
You can't have a dynamic call of hooks due to Rules of Hooks, the better approach is using composition.
Your Choice
component should have a hotKey
and callback
props, the parent should dictate the number of choices.
const Choice = ({ hotKey, callback }) => {
useHotKey(hotKey, callback);
return <>...</>;
};
// Usages
<Choice hotKey="1" callback={()=>console.log('1')} />
choices.map((_,index) => <Choice key={index} hotKey={index} ... />);
See Thinking in React, and HMR's code example.
Upvotes: 3