React : best way to inject Component in dynamically loaded HTML? - reactjs

I'm new on React (I more at ease w/ jQuery or AngularJS). I have a special case and I don't find a good way to resolve it...
My app contains an area which is like a "document viewer". It loads an HTML content from the backend (via API, using Fetch) and inject it in the "viewer" component. The HTML content loaded looks like an "university report" (it's just a formatted text, only <span> and <p> with class="..." attributes, nothing more).
Ex : <p>Lorem ispum <span>some text</span> loreb bis <span>ipsum</span></p> ...
I load the content, and inject it this way in the render() of my component <Viewer> :
<div dangerouslySetInnerHTML={ getFreshlyLoadedHTML() } />
Easy, it works just fine !
But... Now, I want to inject some "interactive" components in the loaded HTML. For example, some button to give a feedback etc. The API must decide where to place the component between the words/nodes of the formatted text (HTML).
Ex :
<p> Lorem ispum <span>some text</span>
loreb bis <span>ipsum</span>
<MyFeedbackButton paragraph="1.3"/>
</p><p>Other Lorem Ipsum<p><span>...</span>
There, I'm stucked because I cannot use dangerouslySetInnerHTML if there are components inside the loaded HTML...
First attempt : I've tried modifying the API, and instead of sending the HTML in a string to the app, I send a custom JSON structure that represents almost the final JSX structure that I want. Then, in my react page, the render function only have to parse the JSON and build the JSX (here, a JsFiddle example if it's not clear : https://jsfiddle.net/damienfa/69z2wepo/34536/ )
It works, but I can't believe it's the good way...
I see a major problem : all the HTML node (span, p...) that I build from the render function are referenced by reactJs, is it really necessary ? Mostly, there are "dead" nodes (I mean, dom node that won't never changed, this is static formatted text).
Just take a look a all those "data-reactid" on nodes that never will be interactive...
What would be your advice on that case ?
What about my attempt with a JSON-structure sent by the API ?
Is there a way to say to react "do not reference that element" ?
Do you clearly see a better solution to my problem ?

Your current workflow is not very secure and subject to many potential errors and open doors, especially concerning code injection ...
The overload due to react tracking the nodes is not an issue, React could track 10 000 nodes and not have a problem (well actually on many of my apps React has more than 100 000 nodes to care about and it still rurns perfectly).
I see different solutions here:
If there are only 3 or 4 possibilities of dynamic components and order, you might have components like "templates" to which you would simple send text arguments. This is the safest and easiest option.
If it doesn't suit your use-case but the JSON file can contain only a limited set of components, the components should be located in your main app, and then rendered with custom props from the JSON. Actually given the structure of data you could consider using xml instead of json and build a xml tree that you would parse and render. Only components from your white list would be rendered and it would limit drastically the potentials security issues. If needs quite some work on the XML parser though.
If the JSON file can contain many many different and unpredictable components or if the behaviour of those components is largely dynamic and independant of your app, you might as well consider using an iframe, with its own JS and HTML, so that this part of the code is isolated from the rest.

Try using an inline anonymous function within the inner content from within React using JSX. It works! Just be careful about how you wire up the data so there isn't a route where a user can inject HTML from an input or text field.
<div className="html-navigation-button">{(() =>
{
const CreateMarkup = ( sNavItemName :string ) => {
return {__html: sNavItemName };
}
var sTextToAddHtmlTo = props.nextNavItem.name.toString();
sTextToAddHtmlTo = sTextToAddHtmlTo.replace( "/", "/<wbr>" );
return (
<div dangerouslySetInnerHTML={CreateMarkup( sTextToAddHtmlTo )} >
</div>
);
})()}
</div>
I didn't override the React internals of 'render()', but only used a React Component with props wiring to pass down data to it for rendering.
I added the hook for 'dangerouslySetInnerHTML' deep within the return content of the React Component so there would be no easy way to intercept and manipulate it.
As such, there is no 100% guarantee on safety, but that's where adding good security to web services, databases, and use of CORS and CORB would be helpful to lock down security risks.

Related

React - Insert an image between the 2nd and 3rd <p> tag

I have a React component
<Text>
<div dangerouslySetInnerHTML={{ __html: apiContent}} />
</Text>
that displays HTML coming from an API that may look like the following:
<div>
<p>text</p>
<p>more text</p>
<p>more text</p>
<p>more text</p>
<p>still more text</p>
</div>
How do I insert an image, which is another React component, between the 2nd and 3rd p tag?
I know how to do it in vanilla JS, but have trouble doing it the React way.
I'd be curious to see the other answers, but I feel like inserting a React component inside some raw HTML is pretty much impossible. The options I can think of are:
use some library to turn the raw HTML into a tree of React components, and hopefully this library will also include a way to inject custom components in specific places (I don't know any such library, but there probably is one)
edit the raw HTML to insert an empty container with a unique id at the place you need it (for instance by parsing the HTML into a DocumentFragment, using vanilla JS to insert a div, turning it back into a string, and setting it as innerHTML as you already do), and then use a React portal to inject a React component into that container. Pretty messy...
If it is a react component then it is not html so not logical to put it inside dangerouslySetInnerHTML tag.
you need to rewrite this react component to an HTML string and work with the html that is coming from API as a string.
that means you can split the incoming string of html on the second closing p tag
and add the new html string in the middle.

Testing workflow with Cypress and React

I have question regarding the development and testing workflow. I am using Cypress but this topic is suitable for any end to end test.
The question is how do you selecting the elements in the browser?
1, Explicit selectors like data-cy or automation-id on each element or component.
2, Selecting the elements by visible text on the screen and then navigate to specific element by DOM hierarchy.
Every test you write will include selectors for elements. To save yourself a lot of headaches, you should write selectors that are resilient to changes.
Oftentimes we see users run into problems targeting their elements because:
Your application may use dynamic classes or ID's that change
Your selectors break from development changes to CSS styles or JS behavior
Luckily, it is possible to avoid both of these problems.
Don't target elements based on CSS attributes such as: id, class, tag
Don't target elements that may change their textContent
Add data-* attributes to make it easier to target elements
<button
id="main"
class="btn btn-large"
name="submission"
role="button"
data-cy="submit"
>
Submit
</button>
And then for example clicking to button
cy.get("[data-cy=submit]")
.should("be.visible")
.click()
You can also search for specific text in dom.
cy.get("button")
.should("be.visible")
.contains("Submit")
.click()
Custom commmands commands.js
Cypress.Commands.add("sendBtn", () => {
cy.get("[data-cy=cy_send_btn]")
.should("be.visible")
.click()
})
And in test file
it("Add test description here", function() {
.
.
.
.
cy.sendBtn()
})
Custom command shown above you will be able to use multiple times in other test files for all send buttons. Your tests will be more isolated and efficient.

Next.JS Static Generation not optimal for SEO when using `map` method

I am currently using Next.JS to create a static website with the main objective to have a very good SEO-optimized website.
Everything works fine and the website is correctly deployed with Vercel, but I have noticed that part of the content is not present directly in the HTML files.
For instance, I have a component that loops over an array of data, using the array map method, like this:
{imageTexts.map((image) => (
<ImageText
key={image.title + 'TitleImage'}
title={image.title}
description={image.description}
size={imagesSize}
image={image.image}
/>
))}
Once the website is deployed to Vercel, I search inside the HTML file for the information/strings contained in the array of data (imageTexts), but I can't find them. I guess Next.JS uses javascript to target some sort of div and then loops over its own JSON file to dynamically display content.
For me, this seems to kill a lot of the SEO advantage that static websites have over SPA. Is there any way I can have those strings directly inside my HTML files?
I am still not 100% sure this is caused by the map method, but I don't find any other explanations. Especially because other dynamically loaded components don't have the same problem. For example, this component string can be found on the HTML file, without a problem:
{title ? (
<Text
type="h2"
textAlign="center"
>
{title}
</Text>
) : null}
If you are mapping over ImageTexts on the server and that component renders HTML tags, then that HTML should be sent on the first-page load, and you could see it if you do CTRL+U or disable javascript.
Ok, I have just found that the reason. It has nothing to do with the map method. I was actually using the <Remark> component from library called react-remark. It seems it does not play well with Next.JS

Using HTML placeholders in React

I grab content from a CMS via Gatsby. Inside the markup there are placeholders which should be replaced with React elements.
So I get sth like this:
let content = '<span>Hello World [placeholder]!</span>';
and in React I want to change it to sth like this (where the markup for the tooltip comes from a React element):
let content = '<span>Hello World <div class="tooltip">Important warning</div>!</span>';
The final html with the replaced elements should be dumped into the DOM using dangerouslySetInnerHTML.
I tried using react-string-replace:
reactStringReplace(content, '[placeholder]', () => (<Tooltip />));
but it gives me an array containing a mix of strings and React elements that can't be concacenated without breaking the HTML structure.
What would be a good approach to tackle this issue? Or am I wrong using placeholders in the CMS altogether?
i found a really good npm package that provides this functionality and much more: https://interweave.dev/

Getting wordpress posts with react shows special chars instead of apostrophe

I am getting what I am assuming is json data from a wordpress blog endpoint like so:
https://example.com/wp-json/wp/v2/posts
I am looping through and showing the tiles for now:
<div>{posts && posts.map((post) => <h1>{post.title.rendered}</h1>)}</div>
But the post titles are not displaying properly. For example the word Don't shows Don’t
I have discovered that I can use dangerouslySetInnerHTML to fix this issue but is it safe? The fact that it has the word 'dangerously' in it is worrying.
I believe dangerouslySetInnerHTML is the way to go about this - but I will go into more detail as to why "dangerously" is in "dangerouslySetInnerHTML" and hopefully that will help you make an informed decision for your situation.
What dangerouslySetInnerHTML does is render any HTML string given to it within the DOM element.
For example:
<h1 dangerouslySetInnerHTML={{__html: post.title.rendered}} />
(as an aside, note the __html key has two underscores)
Will properly render the string Don’t to Don't.
This is all pretty harmless, however, if, for example, the value of post.title.rendered could be set by an untrusted party (such as an arbitrary user), and if this arbitrary user wanted to do some damage, they could enter a string such as:
<script type="text/javascript>
// Do evil stuff
console.log('I did some evil stuff');
</script>
This code would then be executed by the browser when the page loads - because React would have generated the following DOM:
<h1>
<script type="text/javascript>
// Do evil stuff
console.log('I did some evil stuff');
</script>
</h1>
So with all that in mind, if you are sure that the value of this field is within your control (and not anyone else's) and you also know that there will not be any arbitrary code in these strings, then go ahead and use dangerouslySetInnerHTML.
However, if there is the possibility that someone besides yourself could manipulate this field, I would instead look to something like decode-html-entities - this way you can have the presentation you want, without compromising your app/users.

Resources