I have onClick event which 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 am expecting 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 which 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>
)
}
CodePudding user response:
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>
CodePudding user response:
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.
CodePudding user response:
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>