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:
CodePudding user response:
You can solve your problem using useRef hook. Here is my solution (In this solution, I used a new ref variable selectedElementRef.):
import { useEffect, useState, useRef, ChangeEvent } from "react";
function App() {
const [inputValue, setInputValue] = useState("Initial value");
const [elements, setElements] = useState<HTMLElement[]>([]);
const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
null
);
const selectedElementRef = useRef(selectedElement)
selectedElementRef.current = selectedElement
function findElementsByText(selectors: string, text: string) {
if (text === "") return [];
const regex = new RegExp(text);
const elements = Array.from(
document.querySelectorAll<HTMLElement>(selectors)
);
console.log(elements)
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",
selectedElementRef.current
);
}
}
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;