Highlights all case-insensitive occurrences of query in text with yellow mark styling. Results are memoized by a cache key incorporating text hash and query to avoid redundant processing on repeated calls with the same inputs and prevent collisions from same-length but different text strings.

export const highlightText = (text: string, query: string): React.ReactNode => {
if (!query.trim()) {
return text;
}

// Create cache key incorporating text hash, length, and query for low-collision lookup
const cacheKey = `${hashText(text)}|${text.length}|${query.toLowerCase()}`;
if (highlightCache.has(cacheKey)) {
const cachedResult = highlightCache.get(cacheKey)!;
// Move to end of Map by deleting and re-setting to make eviction closer to LRU
highlightCache.delete(cacheKey);
highlightCache.set(cacheKey, cachedResult);
return cachedResult;
}

const lowerText = text.toLowerCase();
const lowerQuery = query.toLowerCase();
const parts: React.ReactNode[] = [];
let lastIndex = 0;
let matchIndex = 0;

// Find all occurrences of query in text
let index = lowerText.indexOf(lowerQuery);

while (index !== -1) {
// Add text before the match
if (index > lastIndex) {
parts.push(sliceText(text, lastIndex, index));
}

// Add highlighted match
parts.push(
React.createElement(
"mark",
{
key: `match-${matchIndex}`,
className:
"rounded bg-yellow-200/80 px-1 text-yellow-900 dark:bg-yellow-500/30 dark:text-yellow-100",
},
sliceText(text, index, index + query.length),
),
);

// Move to next position
lastIndex = index + query.length;
index = lowerText.indexOf(lowerQuery, lastIndex);
matchIndex++;
}

// Add remaining text after last match
if (lastIndex < text.length) {
parts.push(sliceText(text, lastIndex, text.length));
}

const result = React.createElement(React.Fragment, null, ...parts);

// Store in cache, evicting oldest entry if cache is full
if (highlightCache.size >= CACHE_SIZE_LIMIT) {
const firstKey = highlightCache.keys().next().value;
if (firstKey) {
highlightCache.delete(firstKey);
}
}
highlightCache.set(cacheKey, result);

return result;
};
  • Parameters

    • text: string

      The text to highlight.

    • query: string

      The search query to find and highlight.

    Returns ReactNode

    React nodes array containing text and mark elements; original text if query empty.