0

I have an onClick event that comes from a String as follows.

const text = `some text and <sup onClick={handleClick}>123</sup>`;

This text is actually coming from an external API response.

And I have a handleClick function as follows:

const handleClick = () => alert('clicked!');

This handleClick function is in my component. I expect the alert to occur when the sup text 123 is clicked.

The text is clickable, but when I click it, I get the error:

Unhandled Runtime Error
ReferenceError: handleClick is not defined

Call Stack
HTMLElement.onclick

But handleClick is there in the component.

I even validated it with a button that doesn't use the text. That works fine.

Why doesn't it work for the text and how could I resolve this?

Component

export default function Home() {

  const handleClick = () => alert('clicked!');

  const text = `some text and <sup onClick={handleClick}>123</sup>`; // assume this comes from an api.
  
  return (
    <div>
      <div dangerouslySetInnerHTML={{ __html: text }} /> // this is the line throwing error when I click it.


      // this button shows the alert fine when clicked.
      <button
        onClick={handleClick}
      >
        Click me now!
      </button>
    </div>
  )
}
12
  • Maybe this has something to do with react's virtual dom being a different env than the window object normally? Try something like adding a one time useEffect that adds the function to the global scope? And is it possible to not have the inner html be jsx? Commented Jan 30, 2023 at 23:52
  • 1
    Pretty sure that dangerouslySetInnerHTML expects HTML and not JSX in which case onClick={handleClick} doesn't mean anything. It would need to be onclick="handleClick()" as you would pass to a regular HTML element. Commented Jan 30, 2023 at 23:55
  • You might look at the legacy createElement (but asyncawaits comment re: scoping will still need to be looked into) Commented Jan 31, 2023 at 0:02
  • Thanks both. A combination worked where I do have to set the function to window and onclick="handleClick() worked. Having a look at createElement. Just checking the cleanest way to do this cos setting the function to window would need useEffect as window may not be available from the start. Was trying to avoid having to use useEffect. Commented Jan 31, 2023 at 0:03
  • 1
    @asyncawait Function name comes from api. The function implementation occurs on front end. State is just to open close a modal. Thus not doing anything specific to parse and fit data to children. Commented Jan 31, 2023 at 0:47

3 Answers 3

1

Here's an example using altered api txt with onclick being html and not jsx, as well as adding the function to global scope using uesEffect

const {useState, useEffect} = React;

const Home = () => {
  const handleClick = () => alert('clicked!');
  const text = `some text and <sup onClick="handleClick()">123</sup>`;
  useEffect(() => {
    window.handleClick = handleClick;
  }, []);
  return (
    <div>
      <div dangerouslySetInnerHTML={{ __html: text }} />

      <button
        onClick={handleClick}
      >
        Click me now!
      </button>
    </div>
  );
};

ReactDOM.createRoot(
    document.getElementById("root")
).render(
    <Home />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Here's an example without useEffect that would listen to the parent for which child was clicked, if you have the ability to shape used data to have a data attribute with which function is meant to be hit.

const {useState, useEffect} = React;

const Home = () => {
  const handleClick1 = () => alert('clicked1!');
  const handleClick2 = () => alert('clicked2!');
  const handleClick3 = () => alert('clicked3!');
  const text1 = `1 text and <sup data-func="func1">123</sup>`;
  const text2 = `2 text and <sup data-func="func2">123</sup>`;
  const text3 = `3 text and <sup data-func="func3">123</sup>`;
  const handleAllClicks = e => {
    const func = e.target.dataset.func;
    switch (func) {
      case "func1": return handleClick1();
      case "func2": return handleClick2();
      case "func3": return handleClick3();
    }
  };
  return (
    <div onClick={handleAllClicks}>
      <div dangerouslySetInnerHTML={{ __html: text1 }} />
      <div dangerouslySetInnerHTML={{ __html: text2 }} />
      <div dangerouslySetInnerHTML={{ __html: text3 }} />
    </div>
  );
};

ReactDOM.createRoot(
    document.getElementById("root")
).render(
    <Home />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Sign up to request clarification or add additional context in comments.

1 Comment

ideally I would avoid dangerously set, and just have the data which decides which function to use lookup the desired function of front end and place that in a component.
1

When you dangerouslySetInnerHTMl you are setting HTML, not React JSX. Your div will be rendered into the HTML DOM something like this:

<div>
  some text and <sup onclick="{handleClick}">123</sup>
</div>

In essence, this is a regular HTML gross onclick property that's executed in the context of the window as pure JS, not React. The curly braces are still valid JS, but there is no defined property handleClick, so you get a reference error.

Try running the following snippet and inspect the HTML and you'll see what I mean:

function Home() {
  const handleClick = () => alert('clicked!')

  const text = `some text and <sup onClick={handleClick}>123</sup>`
  
  return (
    <div>
      <div dangerouslySetInnerHTML={{ __html: text }} />
      <button
        onClick={handleClick}
      >
        Click me now!
      </button>
    </div>
  )
}

ReactDOM.createRoot(document.getElementById("root")).render(<Home />)
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>

The only thing I can think of is using the React create element instead:

function DataRender({ data, onClick }) {
  return React.createElement(
    "div",
    null,
    ...data.map(seg => {
      if(!seg.clickable) return seg.value
      return React.createElement(
        "sup",
        { onClick },
        seg.value
      )
    })
  )
}

function Home() {
  const handleClick = () => alert('clicked!')

  // change your API to give JSON responses or manually process
  const data = [{ value: "some text and " }, { value: "123", clickable: true }]
  
  return (
    <div>
      <DataRender data={data} onClick={handleClick} />
      <button
        onClick={handleClick}
      >
        Click me now!
      </button>
    </div>
  )
}

ReactDOM.createRoot(document.getElementById("root")).render(<Home />)
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>

Comments

0

The problem here is that you are trying to access the handleClick function inside a string, which is not possible as JavaScript evaluates the string as plain text. To resolve this issue, you need to parse the string as HTML and dynamically bind the event handler to the element in the DOM. You can use a library like React-DOM-Parser to parse the string as HTML and bind the event handler to the element.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.