User text containing ., $ or ( must be escaped before you splice it into new RegExp(). RegExp.escape(str) (ES2025, Baseline since May 2025) backslashes syntax characters and writes other punctuators, whitespace and a leading letter or digit as \xHH, so the result stays literal in any position.
const term = '$9.99 (sale)';
const text = 'Now $9.99 (sale), was $12.00';
console.log(RegExp.escape(term), RegExp.escape('a.b*c'), RegExp.escape('x-y'));
console.log(text.replace(new RegExp(RegExp.escape(term), 'g'), '[$&]'));
const legacyEscape = (s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&'); // pre-2025 helper
console.log(legacyEscape(term), new RegExp(term).test(text)); // unescaped: no match\$9\.99\x20\(sale\) \x61\.b\*c \x78\x2dy Now [$9.99 (sale)], was $12.00 \$9\.99 \(sale\) false
For older browsers, keep a helper like legacyEscape or the MIT-licensed escape-string-regexp 593 (https://github.com/sindresorhus/escape-string-regexp 593 ) package, which also writes - as \x2d.