Tengo una situación en la que necesito analizar una cadena, romperla, volver a unirme y luego generar jsx a partir de ella. Tengo algo de éxito, pero también tengo que hacer que se pueda hacer clic para poder navegar al hacer clic, pero el problema es que onClick sigue siendo una cadena incluso después de la conversión.
Aquí están los detalles:
Obtengo una cadena en este formato:
Algo de texto @(nombre de usuario)[a_long_user_id_to_navigate_to] algo más de texto
Primero obtengo todas las menciones del texto y las convierto en objetos de nombres de usuario con sus ID.
y entonces:
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 } el argumento del message en la función anterior contiene la cadena sin procesar y las mentions son una matriz de objetos de la siguiente manera:
mentions = [ { username: 'abcd_1234', id: 'abcd_defg_hijk' }, { username: 'efgh_1234', id: 'wxyz_defg_jklm' } ...so on ]Esto es lo que hago cuando agrego este texto para verlo:
<p dangerouslySetInnerHTML={{__html: message}}></p>
Esto es lo que veo en la página web:
Some text @username some more text
Esto es lo que veo en Inspector:
<span style="color:#03F29E; cursor: pointer" onclick="{props.history.push('/some-route')}">@username</span>
Ahora la pregunta es cómo manejo esta parte onclick="{props.history.push('/some-route')} para que funcione de la forma de reacción. Además, ¿lo estoy haciendo correctamente o hay una mejor manera?
Podrías hacerlo así:
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 se necesitan travesuras html internas. Solo úsalo así:
<p>{message}</p>Puede agregar controladores de clics a esos tramos usando Vanilla JS de la siguiente manera:
user-mention const createMessageWithActionableMentions = (message, mentions) => { mentions.map(mention => { message = message.replace('@'+'['+mention.username+']'+'('+mention.id+')',`<span style="color:${Colors.colorPrimary}; cursor: pointer" class="user-mention">@${mention.username}</span>`) }) return message } useEffect(() => { [...document.getElementsByClassName('user-mention')].forEach((element) => { element.addEventListener('click', () => { props.history.push('/some-route'); }) }) }, [/* Your dependencies */])Tenga en cuenta que si los intervalos cambian (aumentan/disminuyen en número, por ejemplo), también debe manejar los detectores de eventos cuando cambien. Si esto se vuelve engorroso, puede usar la delegación de eventos
Lo descubrí, esto se puede resolver usando la matriz, simplemente primero dividí el mensaje en una matriz de subcadenas:
message = message.split(' ') Luego lo asignó para encontrar menciones en esas subcadenas y reemplazó la substr respectiva con <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> } } })el codigo final
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 }Luego lo representó como jsx
<p>{message}</p>