I have a situation where I need to parse a string, break, rejoin and then generate jsx from it. I'm somewhat successful but I have to make it clickable as well so that I can navigate on click but the problem is onClick
remains a string even after conversion.
Here's the details:
I get a string in this format:
Some Text @(username)[a_long_user_id_to_navigate_to] some more text
I first get all the mentions from text and convert it to object of usernames with their IDs.
and then:
const createMessageWithActionableMentions = (message, mentions) => {
mentions.map(mention => {
message = message.replace('@' '[' mention.username ']' '(' mention.id ')',`<span style="color:${Colors.colorPrimary}; cursor: pointer" onClick={props.history.push('/some-route')}>@${mention.username}</span>`)
})
return message
}
the message
argument in above function contains the raw string and the mentions
is an array of objects as follows:
mentions = [
{
username: 'abcd_1234',
id: 'abcd_defg_hijk'
},
{
username: 'efgh_1234',
id: 'wxyz_defg_jklm'
}
...so on
]
Here's what I do when I add this text to view:
<p dangerouslySetInnerHTML={{__html: message}}></p>
Here's what I see on webpage:
Some text @username some more text
Here's what I see on Inspector:
<span style="color:#03F29E; cursor: pointer" onclick="{props.history.push('/some-route')}">@username</span>
Now the question is how do I handle this part onclick="{props.history.push('/some-route')}
to work the react way. also, am I doing it correctly or there's a better way?
CodePudding user response:
You can add click handlers to those spans using Vanilla JS as following:
- Assign a class to those spans, for example
user-mention
const createMessageWithActionableMentions = (message, mentions) => {
mentions.map(mention => {
message = message.replace('@' '[' mention.username ']' '(' mention.id ')',`<span style="color:${Colors.colorPrimary}; cursor: pointer" >@${mention.username}</span>`)
})
return message
}
- Add event listeners to those spans. If you are using function components with hooks, put it inside a useEffect like this, and make sure this effect hook is called after the spans are appended to the DOM.
useEffect(() => {
[...document.getElementsByClassName('user-mention')].forEach((element) => {
element.addEventListener('click', () => {
props.history.push('/some-route');
})
})
}, [/* Your dependencies */])
Note that if the spans change (increase/decrease in number, for example), you need to handle event listeners when they change as well. If this becomes cumbersome, you can use Event delegation
CodePudding user response:
You could do it like this:
const createMessageWithActionableMentions = (message, mentions) => {
// Will return a list of JSX elements
let output = [message];
// For every mention
mentions.map(
mention=>`@[${mention.username}](${mention.id})`
).forEach(mention => {
// For every string we have output so far
output = output.map(
substring => {
// Exclude existing JSX elements or strings that do not contain the mention
if (typeof substring!=="string" || !substring.includes(mention)) return [substring]
// We know the mention exists in this specific string segment so we need to find where it is so we can cut it out
const index = substring.indexOf(mention)
// Split the string into the part before, the part after, and the JSX element
return [
substring.substring(0,index),
<span style={{color:Colors.colorPrimary; cursor: "pointer"}} onClick={()=>props.history.push('/some-route')}>{mention.username}</span>,
substring.substring(index mention.length)
]
}
// Reduce the nested array of arrays back into a single array
).reduce((a,b)=>[...a,...b], [])
})
return output
}
No Inner html shenanigens needed. Just use it like this:
<p>{message}</p>
CodePudding user response:
I figured it out, this can be solved using the array, I simply first broken the message into array of substrings:
message = message.split(' ')
Then mapped it to find mentions in those substrings and replaced the respective substr
with <span>
:
message.map( (substr, index) => {
if(isMention(substr))){
found = mentions.find(mention => mention.username == substr.split('[').pop().split(']')[0])
if(found) {
message[index] = <span style={{color:Colors.colorPrimary, cursor: "pointer"}}>{found.username}</span>
}
}
})
The final code
const createMessageWithActionableMentions = (message, mentions) => {
let found = ''
message = message.split(' ')
message.map( (substr, index) => {
if(isMention(substr))){
found = mentions.find(mention => mention.username == substr.split('[').pop().split(']')[0])
if(found) {
message[index] = <span style={{color:Colors.colorPrimary, cursor: "pointer"}}>{found.username}</span>
}
}
})
return message
}
Then rendered it as jsx
<p>{message}</p>