טיפ ריאקט: טיפול באירועים גלובאליים
בריאקט מאוד קל לטפל באירועים ברמת הקומפוננטה - צריך רק להוסיף מאפיין on
ושם האירוע. למשל בשביל לטפל באירוע keydown על אלמנט בתוך קומפוננטת ריאקט אני יכול לכתוב:
export default function App() {
const [keys, setKeys] = useState("");
function handleKeyDown(e) {
setKeys(keys + e.key);
}
return (
<div className="App" onKeyDown={handleKeyDown}>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<p>{keys}</p>
<input type="text" />
</div>
);
}
אבל זה רק חצי עובד: קוד כזה יפעיל את פונקציית הטיפול באירוע רק אם משהו בתוך הקומפוננטה נמצא בפוקוס. לכן כשתיבת הטקסט בפוקוס ואנחנו כותבים שם טקסט גם משתנה ה state יתעדכן, אבל במצבים אחרים כשאני פשוט לוחץ על כפתורים הקומפוננטה תתעלם מהלחיצות.
הפיתרון כאן הוא הרבה יותר קל ממה שאפשר לדמיין. אנחנו הרי יודעים שכל אירוע גלובאלי מגיע ל body, ובפרט אירועי keydown, כשאין אף אלמנט בפוקוס פשוט מטופלים ברמת ה body. כל מה שצריך לכן הוא להוסיף אפקט שיתחבר לאירוע ה keydown של ה body בכניסה למסך של הקומפוננטה, ויתנתק ביציאה.
בשביל שיהיה נוח להשתמש בקוד מכל מקום ביישום, אני אוהב לכתוב קוד "אפקט" כזה בתוך קומפוננטה נפרדת, ואז לשתול את הקומפוננטה בכל מקום שצריך אותה. וכן, אפשר גם לכתוב Custom Hook ולקבל בדיוק את אותו אפקט. זה הקוד אחרי התיקון:
import { useEffect, useState } from "react";
import "./styles.css";
function GlobalKeyboardEvents({ onKeyDown }) {
useEffect(() => {
document.body.addEventListener("keydown", onKeyDown);
return function () {
document.body.removeEventListener("keydown", onKeyDown);
};
}, [onKeyDown]);
return <></>;
}
export default function App() {
const [keys, setKeys] = useState("");
function handleKeyDown(e) {
setKeys(keys + e.key);
}
return (
<div className="App" >
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<p>{keys}</p>
<input type="text" />
<GlobalKeyboardEvents onKeyDown={handleKeyDown} />
</div>
);
}
ואפשר לשחק איתו לייב בארגז החול בקישור: https://codesandbox.io/s/frosty-violet-llfo2g?file=/src/App.js:0-711