2
\$\begingroup\$

I'm writing a ctrl+f function so to speak in React that allows a user to search for a term in a list of words, and if the term matches any of the words in the list then it highlights it. To do this, I'm currently using document.querySelectorAll to retrieve all of the words in the list and manipulate them (highlight) if there is a match. Because this is React I feel very bad about using the querySelector to grab the words and feel I should be using refs perhaps, but not sure how comparable that is or if that works in this particular scenario. Here is my code:

const HighlightSearchTerm = () => {
 const [searchTerm, setSearchTerm] = React.useState("");
 React.useEffect(() => {
 const regex = new RegExp(searchTerm, "gi");
 const labels = document.querySelectorAll(".label"); // use refs??
 labels.forEach(label => {
 label.innerHTML = label.innerText.replace(regex, match =>
 `<span class="highlight">${match}</span>`
 );
 });
 }, [searchTerm]);
 return (
 <div>
 <input 
 type="text"
 value={searchTerm}
 placeholder="Search Labels"
 onChange={event => setSearchTerm(event.target.value)}
 />
 </div>
 );
};

As you can see, implementing it this way has to also call document.querySelectorAll each time the user types a letter or makes a change which isn't very efficient and is causing [Violation] 'setTimeout' handler took <N>ms to appear in the console many times when working with large lists. Instead, I would like to

  1. Not use document.querySelectorAll to retrieve the words in the list (use refs if possible?)
  2. Not fetch the list of words each time I type due to the fact the list won't change once the component is rendered (possibly render them when the component mounts, although it seems I can't do this currently when using querySelector)

Any other optimization suggestions would also be very much appreciated.

Current working example: https://codepen.io/andrewgarrison/pen/eYOWjZB

200_success
146k22 gold badges190 silver badges479 bronze badges
asked Aug 28, 2019 at 3:00
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Rather than extract the Search input in to a different component I would suggest you keep the input and list in the same component.

This way when printing the list you can highlight the search term directly.

const HighlightableList = (props) => {
 const [searchTerm, setSearchTerm] = React.useState("");
 const regex = new RegExp(searchTerm, "gi");
 const items = props.terms.map((term, index) => (
 <div
 className="label"
 key={index}
 dangerouslySetInnerHTML={{
 __html: term.replace(regex, match => `<span class="highlight">${match}</span>`)
 }}
 />
 ));
 return (
 <>
 <input 
 type="text"
 value={searchTerm}
 placeholder="Search Labels"
 onChange={event => setSearchTerm(event.target.value)}
 />
 <div className="label-container">
 {items}
 </div>
 </>
 );
}

https://codepen.io/rockingskier/pen/YzKrGNR

Side notes:

Don't make each list item a label. From MDN

The HTML element represents a caption for an item in a user interface.

That would suggest that each item in the list has its own user input. In fact there is only one input (search) and that should really have a label of its own. I'll let you implement that.

Additionally due the the "list-iness" of the data I have converted it to a ul with each item as an li.

answered Sep 2, 2019 at 14:21
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.