The following code searches elements by text. It also sets a selectedElement (the first element in elements).
import { useEffect, useState, ChangeEvent } from "react";
function App() {
const [inputValue, setInputValue] = useState("Initial value");
const [elements, setElements] = useState<HTMLElement[]>([]);
const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
null
);
function findElementsByText(selectors: string, text: string) {
if (text === "") return [];
const regex = new RegExp(text);
const elements = Array.from(
document.querySelectorAll<HTMLElement>(selectors)
);
return elements.filter((element) => {
return element.textContent?.match(regex);
});
}
function handleInputChange(event: ChangeEvent<HTMLInputElement>) {
const selectors = "abbr";
const { value } = event.target as HTMLInputElement;
const foundElements = findElementsByText(selectors, value);
const foundSelectedElement = foundElements[0] || null;
setInputValue(value);
setElements(foundElements);
console.log("selectedElement from handleInputChange", foundSelectedElement);
setSelectedElement(foundSelectedElement);
}
function isCommand(event: KeyboardEvent) {
return event.ctrlKey || event.key === "Enter" || event.key === "Escape";
}
function handleDocumentKeyDown(event: any) {
if (!isCommand(event)) return;
if (event.ctrlKey && event.key === "]") {
console.log("selectedElement from handleDocumentKeyDown", selectedElement);
}
}
useEffect(() => {
document.addEventListener("keydown", handleDocumentKeyDown);
return () => {
document.removeEventListener("keydown", handleDocumentKeyDown);
};
}, []);
return (
<div id="keys">
<input type="text" onChange={handleInputChange} value={inputValue} />
<span id="count">
{selectedElement ? elements.indexOf(selectedElement) + 1 : 0}/
{elements.length}
</span>
<br/>
<abbr>a</abbr>
<abbr>b</abbr>
<abbr>c</abbr>
</div>
);
}
export default App;
I want Ctrl + ] to set selectedElement to the next element. To do that, I have to be able to access selectedElement inside handleDocumentKeyDown.
But if, for example, I type a in the input (triggering handleInputChange and setSelectedElement()), then I press Ctrl + ] (triggering handleDocumentKeyDown), selectedElement will be null, even though foundSelectedElement is <abbr>a</abbr>.
I thought when I pressed Ctrl + ], I'd already be in the "next render" and therefore I'd be able to access the newest value of selectedElement in handleDocumentKeyDown. But it wasn't the case.
How to change the code so that selectedElement isn't null in handleDocumentKeyDown, and instead has the HTML element set by setSelectedElement() in handleInputChange?
Live code:
