Reputation: 643
I have array of values
const tags = ['Hello', 'moment', 'somewhere'];
and the paragraphs to show in my react component
const paras = [{
text: 'Hello, tina. Thank you for all waiting. Hello?'
}, {
text: 'When can I go? Okay. One moment. Let me just'
}, {
text: 'if I can help you somewhere, if you are interested'
}]
I want to highlight the para words with the tags element. And also only need few words before and after highlighted words like in the first sentence I only need to show like
*One **moment**. Let *
How can I do that?
What I have tried:
getHighlightedText(text, highlight) {
// Split text on highlight term, include term itself into parts, ignore case
const parts = text.split(new RegExp(`(${highlight})`, 'gi'));
return <span>{parts.map(part => part.toLowerCase() === highlight.toLowerCase() ? <span className="highlight">{part}</span> : part)}</span>;
}
{
paras.map((para) => {
return (
<Row key={para._id}>
<Col md={11}>
<div className="record">
<p className="mb-0">
{this.getHighlightedText(para.text, "hello")}
</p>
</div>
</Col>
<Col md={1}>
<Button className="buttonLink" color="link">
<b>{"View >"}</b>
</Button>
</Col>
</Row>
);
});
}
Upvotes: 2
Views: 1249
Reputation: 4883
Here is a super simple to use reusable component for text highlighting by tags:
function Highlight({ children: text = "", tags = [] }) {
if (!tags?.length) return text;
const matches = [...text.matchAll(new RegExp(tags.join("|"), "ig"))];
const startText = text.slice(0, matches[0]?.index);
return (
<span>
{startText}
{matches.map((match, i) => {
const startIndex = match.index;
const currentText = match[0];
const endIndex = startIndex + currentText.length;
const nextIndex = matches[i + 1]?.index;
const untilNextText = text.slice(endIndex, nextIndex);
return (
<span key={i}>
<mark>{currentText}</mark>
{untilNextText}
</span>
);
})}
</span>
);
}
export default function App() {
return (
<div className="App">
<h1>
<Highlight tags={["Hel", "co", "nd"]}>Hello CodeSandbox</Highlight>
</h1>
<h2>
<Highlight tags={["ditin", "e"]}>
Start editing to see some magic happen!
</Highlight>
</h2>
</div>
);
}
Using it with your example:
const tags = ["Hello", "moment", "somewhere"];
const paras = [
{
text: "Hello, tina. Thank you for all waiting. Hello?"
},
{
text: "When can I go? Okay. One moment. Let me just"
},
{
text: "if I can help you somewhere, if you are interested"
}
];
function ParasList() {
return (
<ul>
{paras.map((para, i) => (
<li key={i}>
<Highlight tags={tags}>{para.text}</Highlight>
</li>
))}
</ul>
);
}
Explanation:
The Highlight
component is easy to use, you just wrap around some text, and put on the "tags" attribute: <Highlight tags={["some"]}>some text</Highlight>
Inside the component, I'm using the new and shiny string.prototype.matchAll function, which gives us information about the match and the index where it was found.
Also, I'm creating the regex using join("|")
on the tags, so they have an "OR" relationship between them. The flag i
makes the highlight case insensitive, but you could delete it if you would want to keep case sensitivity. The g
flag is the global flag, so duplicate matches are found and highlighted. It is required in a matchAll search.
Lastly, I'm using the <mark>
html tags for highlighting, but you could easily change this, or even pass a custom tag through props for more versatility. You could also just enclose the "marked" bits with a regular span, and mark them with a "highlighted" className, and customize the appearance that way, using CSS.
Upvotes: 6
Reputation: 3560
You can resolve this issue by covert tags to string inside your regexp, so that you will simplify to check the list of words via it, then you need to check parts of your para that is contain any string and put and replace the value direct.
For example:
const reg = new RegExp(highlight.join("|").toLowerCase());
and the result of code:
const getHighlightedText = useCallback((text, highlight) => {
// Split text on highlight term, include term itself into parts, ignore case
const parts = text.split(" ");
console.log(parts);
const reg = new RegExp(highlight.join("|").toLowerCase());
const result = {};
for (let i = 0; i < parts.length; i++) {
result[i] = parts[i] + " ";
}
for (let i = 0; i < parts.length; i++) {
if (reg.test(parts[i].replace(/[^a-zA-Z ]/g, "").toLowerCase())) {
result[i] = <HighlightText part={parts[i]} />;
if (result.hasOwnProperty(i - 1)) {
result[i - 1] = <HighlightText part={parts[i - 1]} />;
}
if (result.hasOwnProperty(i + 1)) {
result[i + 1] = <HighlightText part={parts[i + 1]} />;
}
}
}
return <span>{Object.values(result)}</span>;
}, []);
And this is the DEMO URL
ScreenShot:
NOTE: You can enhance the example code, but its just to explain how to use your code in simple way.
const getHighlightedTextOnlyOne = useCallback((text, highlight) => {
// Split text on highlight term, include term itself into parts, ignore case
const parts = text.split(" ");
const reg = new RegExp(highlight.join("|").toLowerCase());
const result = {};
const alreadyDetect = [];
for (let i = 0; i < parts.length; i++) {
result[i] = parts[i] + " ";
}
for (let i = 0; i < parts.length; i++) {
const checkString = parts[i].replace(/[^a-zA-Z ]/g, "").toLowerCase();
if (reg.test(checkString) && !alreadyDetect.includes(checkString)) {
result[i] = <HighlightText part={parts[i]} />;
if (result.hasOwnProperty(i - 1)) {
result[i - 1] = <HighlightText part={parts[i - 1]} />;
}
if (result.hasOwnProperty(i + 1)) {
result[i + 1] = <HighlightText part={parts[i + 1]} />;
}
alreadyDetect.push(checkString);
}
}
return <span>{Object.values(result)}</span>;
}, []);
const getHighlightedViaStringReplace = useCallback((text, highlight) => {
// Split text on highlight term, include term itself into parts, ignore case
const parts = text.split(" ");
const partsForCheck = text.toLowerCase().replace(/[^a-zA-Z ]/g, "").split(" ");
for (let i = 0; i < highlight.length; i++) {
const index = partsForCheck.indexOf(highlight[i].toLowerCase());
if(index !== -1){
partsForCheck[index] = <HighlightText part={parts[index]} />;
if(index - 1 > 0){
partsForCheck[index - 1] = <HighlightText part={parts[index - 1]} />;
}
if(index + 1 < parts.length){
partsForCheck[index + 1] = <HighlightText part={parts[index + 1]} />;
}
}
}
for (let i = 0; i < partsForCheck.length; i++) {
if (typeof partsForCheck[i] === 'string' || partsForCheck[i] instanceof String){
partsForCheck[i] = partsForCheck[i] + " ";
}
}
return <span>{partsForCheck}</span>;
}, []);
Upvotes: 2