React dangerouslySetInnerHTML not working when using variable - html

I'm creating an SPA using React that searches data and displays results. Each result follows the following model
{
"title": "A Title",
"body": " <li>escaped html&nbsp;<strong>that sould be rendered</strong>.</li>
</ul>"
}
The body property is always an escaped html that should be rendered in a component. This component looks like this:
Code
function SearchResult({ title, body, favourite }) {
return (
<article className="SearchResult">
<section>
<i className={`icon-star${favourite ? ' marked' : ''}`} />
{title}
</section>
<section
dangerouslySetInnerHTML={{ __html: body }}
className="SearchResult-body"
/>
</article>
);
}
but the body of each result is not being rendered correctly, instead, it shows the html as a text
The issue is that it only happens when I create the component passing a variable to the body property
results.map((result, index) => (
<SearchResult key={index} title={result.title} body={result.body} />
))
But if I do this, it works fine
<SearchResult
title="A title"
body=" <li>escaped html&nbsp;<strong>that sould be rendered</strong>.</li>
</ul>"
/>
Why is this different? Is there any preprocessing that I should add to the value before passing it in the property that is added by default when I use the fixed value?
Demo
A demo of this issue can be seen here

It seems like this issue only occurs when you give it an escaped html.
A solution implemented by #sergiotapia involves creating a helper function to unescape the html string to make it work.
htmlDecode(content) {
let e = document.createElement('div');
e.innerHTML = content;
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
<section
dangerouslySetInnerHTML={{ __html: htmlDecode(body) }}
className="SearchResult-body"
/>
However as #brigand mentioned and I'll quote "Unescaping it could allow for XSS attacks and incorrect rendering." so this might not be the perfect solution for this.
See working example

Related

Why does react not compile HTML present in a state

I set a default value of a state to be <b> Hey </b> . Now when I rendered this state on the UI it printed the string instead of Hey wrote in bold.I want to know why it is not working. Why react is not able to interpret the html tag and show the appropriate output
import { useState } from "react";
import "./styles.css";
export default function App() {
const [html, setHtml] = useState("<b>Hey</b>");
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>{html}</div>
</div>
);
}
Output :-
Was expecting the output to be Hey written in bold.
Here's the codesandbox link for better understanding :- https://codesandbox.io/s/heuristic-chaum-vo6qt?file=/src/App.js
Thank you. I just want to know why react is not able to render the HTML tag as HTML tag instead of printing it out.
Because you are rendering a string, not HTML. If you want to render stringified HTML then use dangerouslySetInnerHTML, use caution what you pass through, in other words, you may want to run the string through a DOM purifier first.
export default function App() {
const [html, setHtml] = useState("<b>Hey</b>");
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div dangerouslySetInnerHTML={{ __html: html}} />
</div>
);
}
You are setting the value of html as "<b>Hey</b>" which is a string string that's why it renders that as it is. You can directly assign html to the variable like so:
const [html, setHtml] = useState(<b>Hey</b>);
It's a string and not HTML, to fix that maybe you can insert it in the div as innerHTML ie.
document.querySelector(".divClassName").innerHTML = html

Anchor Tag in Next.js

I'm tryin got use an anchor tag in Next.js
I don't get any console errors when I set it up and click the link, but the page does not jump to the id tag.
This issue on github suggests that people need to figure out a lot of custom code to use anchors. That can't be right.
I have:
const links = [
{ label: 'Solutions', href: '#solutions', id: 'solutions' },
]
<NavLink.Desktop key={index} href={link.href} id={link.id}>
{link.label}
</NavLink.Desktop>
I get no errors, but the page does not jump to the label that has an id of 'solutions'.
Does anyone know how to solve this, or where to go for ideas on how - it can't be intented that complex custom code is required to use an anchor tag?
Chakra UI has a Link component
<Link href='https://chakra-ui.com' isExternal>
Chakra Design system <ExternalLinkIcon mx='2px' />
</Link>
If you use the regular anchor tags
<Link href="#anchor_one">Menu one</Link>
<Link href="#anchor_two">Menu two</Link>
Then you can add the id for the anchors to the sections you want to navigate into
<div id="anchor_one" />
<div id="anchor_two" />
This can be either pages or components.
I hope this helped a little bit.
As said by #juliomalves in the comments, you have to specify the id attribute on the element in which you wish to navigate to. Not on the anchor tag.
The id for the anchor should be set on the element you want to link to, not on the link itself.
The below code works for me in Next.js -
export default function Home() {
return (
<div>
Click
<section
style={{ marginTop: "1000px", marginBottom: "1000px" }}
id="section"
>
<h1>Test</h1>
</section>
</div>
);
}
Your code should look like this -
const links = [{ label: 'Solutions', href: '#solutions', id: 'solutions' }]
<NavLink.Desktop
key={index}
href={link.href}
// id={link.id} - This is wrong, as you're referring to the same element
>
{link.label}
</NavLink.Desktop>
// Rather set the id attribute in a separate div/section element
<div id={link.id}>
<h2>Solutions</h2>
</div>
maybe try
const links = [
{ label: 'Solutions', href: '#solutions', id: 'solutions' },
]
<NavLink.Desktop key={index} href={links[0].href} id={links[0].id}>
{link.label}
</NavLink.Desktop>
since you only have 1 element in the links array, if you have multiple just map through the array
It is possible to scroll to anchor programatically using Router.push:
import { useRouter } from 'next/router'
const Foo = () => {
const { push } = useRouter()
const handleClick = () => {
push("#blah")
}
return (
<div>
<button onClick={handleClick}>Scroll</button>
<div>Foo</div>
<div>Bar</div>
<div id="blah">Blah</div>
</div>
)
}
Next.js recognises that you are passing something that is not a link to a new page and will concat it (in the example #blah) to the end of the URL.
Have a read about Link from next/link its a built in feature.
https://nextjs.org/docs/api-reference/next/link
https://github.com/vercel/next.js/blob/canary/examples/hello-world/pages/index.js#L7

Displaying HTML Body on a page using ReactJS

Say I have the following code in my database (user input):
<html>
<title>Test</title>
<body>testing website</body>
</html>
And I fetched it correctly from my database using ReactJS. How can I display this in say: 'localhost:3000/play'? I don't want it to be rendered as raw data just like the code but I want it to actually render the html body as a website. (Title set to Test, and displays a small text: testing website). How can I do that in ReactJS? I already have /play configured and I just want to know how to display it there in the index.js file. I tried something like <div dangerouslySetInnerHTML={template} /> but it didn't work.
So, I fixed it with the following:
In a separate function:
db.getHTMLBody(key).then(snapshot => {
this.setState({ body: snapshot.val() })
})
then in the render:
<div dangerouslySetInnerHTML={{ __html: this.state.body }} />
Please try :
const innerHtml = { __html: escape(snapshot.val()) }
return(<div dangerouslySetInnerHTML={innerHtml} />)

reactjs dangerouslySetInnerHTML and dynamically adding classes to links

I'm new to reactjs and working on a project that is pushing json data to the template.
json structure
"description" : "Some text with a link and another link",
I propose using the following on the template
<p className='paragraph-margin-bottom-10 text--font-size-14 paragraph--justified' dangerouslySetInnerHTML={{ __html: lang.privacy[0].description }} />
but in terms of the output - I would maybe need to append a set of classes to ALL links. What is the best practice for this
so the links render with the following
<a class="text--font-size-14 hyperlink-primary" href="#">link</a>
I can imagine that many people will not agree with me. You can actually do this. But you shouldn't. It is bad enough that you want to use dangerouslySetInnerHTML. It is possible to parse html but there are many edge cases that you would need to handle.
Either tell your backend that they should return the links with proper classes or target the links inside the description directly with css.
See some similar question like: Using regular expressions to parse HTML: why not?
Using regular expressions to parse HTML: why not?
This is how I would do it. I will write the regex later if you run into some problems. I don't have much time to spare right now. Hope it will help. :)
import React from 'react';
import { render } from 'react-dom';
const htmlFromApi = 'some html from API'
const attachClassesToLinks = (htmlWithLinks) => {
// do something special
return htmlWithLinks
}
const App = () => (
<div>
<h1>My Component</h1>
<p dangerouslySetInnerHTML={{ __html: attachClassesToLinks(htmlFromApi) }} />
</div>
);
render(<App />, document.getElementById('root'));

How to recognize response as JSON?

In my ReactJS, I'm making a fetch to an API, and the JSON body response field is the following:
{
"place": <a href=\"http:\/\/place.com\/
search?q=%23MILKYDOG\" target=\"_blank\">#milkydog<\/a>
and quickly came up with a little comic about it. You can
(and should) follow Naomi on twitter <a href=\"http:\/\
/david.com\/ngun\" target=\"_blank\">#ngun<\/a> "
}
And when I try to render it by simply passing the prop down (fetched) to rather than recognizing the formatting and escape characters, and rendering accordingly to them.
What's happening here is that React is helping you out by not allowing random HTML to be injected in to your app. You'll need to use the dangerouslySetInnerHTML way of injecting the content. This is made difficult on purpose because React is trying to help you avoid XSS attacks. So you have to do this the "dangerous" way so you think about whether you could do this without injecting raw HTML (it looks like you're getting content from a blog system maybe? Probably a good example of the exception to the rule.)
From the React documentation, you can use something like the following:
function createMarkup() {
return {__html: 'First ยท Second'};
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}
e.g. in your case
var fetchedFile = {
"body": `<p><img src=\"http:\/\/media.tumblr.com\
/tumblr_lh6x8d7LBB1qa6gy3.jpg\"\/><a href=\"http:\/\
/citriccomics.com\/blog\/?p=487\" target=\"_blank\">TO READ
THE REST CLICK HERE<\/a><br\/>\n\nMilky Dog was inspired by
something <a href=\"http:\/\/gunadie.com\/naomi\"
target=\"_blank\">Naomi Gee<\/a> wrote on twitter, I really
liked the hash tag <a href=\"http:\/\/twitter.com\/
search?q=%23MILKYDOG\" target=\"_blank\">#milkydog<\/a>
and quickly came up with a little comic about it. You can
(and should) follow Naomi on twitter <a href=\"http:\/\
/twitter.com\/ngun\" target=\"_blank\">#ngun<\/a> `
};
function createMarkup(html) {
return {__html: html};
}
class MyThing extends React.Component {
render() {
return(
<div dangerouslySetInnerHTML={createMarkup(fetchedFile.body)} />
);
}
}
ReactDOM.render(<MyThing />, document.getElementById('content'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="content"></div>
React by default always renders as textNode for javascript variables for security reasons, unless you mention it explicitly.
render() {
return(
<div dangerouslySetInnerHtml={{__html: fetchedFile.body}} />
)
}
Read more on this here