'React js - Add an event listener to an html string

I'm developing a chat application on react js where i have the following:

{messages.map((chat, index) => {
  return (
   <p className="ctext" id={chat.id} dangerouslySetInnerHTML={{ __html: 
  chat.body.replace(/\B(\#[a-zA-Z0-9]+\b)/g, match => `<span className="linkHashtag" onclick={hashTagClicked(${match})}>${match}</span>`) }}>
   </p>
  )
})}

So basically, I have a message map where i replace all tags in a HTML string with a class and trying to add a function (runtime), but when I tried this, the function acts like a string and not like a function:

<span class="linkHashtag" onclick="{" clicktagsearch(#MARKET)="" }="">#MARKET</span>

Any ideas?



Solution 1:[1]

EDIT:

I see this as a better way to accomplish the same result without having to manipulate DOM directly:

const messages = [{ body: 'Hello #world two' }, { body: '#Hello Universe' }];
const reg = /\B(\#[a-zA-Z0-9]+\b)/g;    

const DeclarativeRendering = ({ handler }) => (
      <div>
        {messages.map((chat, index) => {
          return (
            <p className="ctext" id={chat.id}>
              <TaggedString
                str={chat.body}
                onClick={handler}
                style={{ marginRight: '5px' }}
                className="linkHashtag"
              />
            </p>
          );
        })}
      </div>
    );
    
    
    const makeTag = (txt, props) => {
      return <span {...props}>{txt}</span>;
    };
    
    const TaggedString = ({ str, ...props }) => {
      const stringsArray = useMemo(
        () =>
          str
            .split(' ')
            .map((el) => (reg.test(el) ? makeTag(el, props) : el + '\n')),
        [str, props]
      );
      return stringsArray;
    };

You can test both the approaches here: https://stackblitz.com/edit/react-u7k6od?file=src%2FApp.js

// OLD ANSWER

I think style doesn't work due to typos ( HTML and JSX declare attributes in different ways ) and event listeners don't work because you need to attach them manually to the DOM nodes after render, something like this should work:

const messages = [{ body: 'Hello #world two' }, { body: '#Hello Universe' }];
const reg = /\B(\#[a-zA-Z0-9]+\b)/g;

const ImperativeManipulation = ({ handler }) => {


const handleEl = (el) => {
    if (!el) return;
    el.querySelectorAll("span").forEach((child) => (child.onclick = handler));
  };

  return (
    <div ref={handleEl}>
      {messages.map((chat, index) => {
        const newText = chat.body.replace(reg, match => `<span class="linkHashtag">${match}</span>` )
        return (
          <p
            className="ctext"
            id={chat.id}
            dangerouslySetInnerHTML={{
              __html: newText,
            }}
          ></p>
        );
      })}
    </div>
  );
};

But I think there are better ways to accomplish that, avoiding entirely imperative DOM manipulation which is a React antipattern.

NOTE: To avoid memory leaks on remount I used .onclick syntax to attach event listeners, so they will simply be overwritten each time the component remounts, if you want to attach an eventListener to the nodes, you must make sure to clean it up on unmount, so you need to use a useEffect to handle that logic.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1