Render custom React component within HTML string from server - javascript

I have a HTML string that comes from the server, for example:
const myString = '<p>Here goes the text [[dropdown]] and it continues</p>`;
And I split this string into 3 parts so the result is the following:
const splitString = [
'<p>Here goes the text ',
'[[dropdown]]',
' and it continues</p>'
];
Then I process those 3 parts in order to replace the dropdown with a React component:
const processedArr = splitString.map((item) => {
if (/* condition that checks if it's `[[dropdown]]` */) {
return <Dropdown />;
}
return item;
}
So after all, I get the processed array, which looks like this:
['<p>Here goes the text ', <Dropdown />, ' and it continues</p>']
When I render that, it renders the HTML as a text (obviously) with the Dropdown component (that renders properly) in between the text. The problem here is that I cannot use { __html: ... } because it has to be used such as <div dangerouslySetInnerHTML={{ __html: ... }} />. I cannot add <div> around the string because that would cut out the <p> tag.
I thought about splitting the parts into tags and then in some sort of loop doing something like:
React.createElement(tagName, null, firstTextPart, reactComponent, secondTextPart);
but that would require fairly complex logic because there could be multiple [[dropdown]]s within one <p> tag and there could be nested tags as well.
So I'm stuck right now. Maybe I'm looking at the problem from a very strange angle and this could be accomplished differently in React. I know that React community discourages rendering HTML from strings, but I cannot go around this, I always have to receive the text from the server.
The only stackoverflow question I found relevant was this one, however that supposes that content coming from backend has always the same structure so it cannot be used in my case where content can be anything.
EDIT:
After some more digging, I found this question and answer which seems to be kinda solving my problem. But it still feels a bit odd to use react-dom/server package with its renderToString method to translate my component into a string and then concatenate it. But I'll give it a try and will post more info if it works and fits my needs.

So after playing with the code, I finally came to a "solution". It's not perfect, but I haven't found any other way to accomplish my task.
I don't process the splitString the way I did. The .map will look a bit different:
// Reset before `.map` and also set it up in your component's constructor.
this.dropdownComponents = [];
const processedArr = splitString.map((item) => {
if (/* condition that checks if it's `[[dropdown]]` */) {
const DROPDOWN_SELECTOR = `dropdown-${/* unique id here */}`;
this.dropdownComponents.push({
component: <Dropdown />,
selector: DROPDOWN_SELECTOR
});
return `<span id="${DROPDOWN_SELECTOR}"></span>`;
}
return item;
}).join('');
Then for componentDidMount and componentDidUpdate, call the following method:
_renderDropdowns() {
this.dropdownComponents.forEach((dropdownComponent) => {
const container = document.getElementById(dropdownComponent.selector);
ReactDOM.render(dropdownComponent.component, container);
});
}
It will make sure that what's within the span tag with a particular dropdown id will be replaced by the component. Having above method in componentDidMount and componentDidUpdate makes sure that when you pass any new props, the props will be updated. In my example I don't pass any props, but in real-world example you'd normally pass props.
So after all, I didn't have to use react-dom/server renderToString.

How about break the text apart and render the component separately?
your react component should look like this (in JSX):
<div>
<span>first text</span>
{props.children} // the react component you pass to render
<span>second part of the text</span>
</div>
and you would just call out this component with something like:
<MessageWrapper>
<DropdownComponent/> // or whatever
</MessageWrapper>

Related

Appending JSX in React Native

I'm searching for a way to add a JSX element programmatically in React Native. Not conditionally.
It needs to be independent function, so far I couldn't find anything about this. Let me give you code example;
const appendJSX = () => {
const jsx = <Text> Hello! </Text>
append(jsx) // <---- can we do something like this?
}
let's say I call this function with useEffect and it should add whatever jsx I have inside the function. Up until this point I always see things like pushing inside of an array or something like that.
UPDATE
Equivalent behaviour that works on web;
useEffect(() => {
const div = document.createElement("div");
div.innerText = "appended div"
document.body.append(div)
}, [])
As you can see we don't have to touch any JSX in application. Reaching document.body and appending whatever we want is possible in React Web. But how can we achieve this in React Native?
Not quite sure what you want to do, but as for to add a JSX manually. Here's the answer.
JSX is already part of the language in most of the cases.
const a = <Text />
export default a
Will translates into:
const a = createElement(Text, null, null)
export default a
Therefore in most of common cases, if you continue using React somewhere else, then the variable a holds a React element without any compilation error.
You might wonder what a actually really holds, it's an object:
const a = {
$$typeof: Symbol(ReactElement),
props: null,
type: Text
}
So you can see the only dependencies in above piece is Text and the ReactElement Symbol. As long as you can resolve them, you are good to export this to anywhere. The latter is normally taken care by Babel.
NOTE:
There's a difference between Text and <Text />. If you just want to export a Text which is a function component, there'll tutorial online, also you can dig into any third party library, because essentially that's what they do, export Text so other people can use it.

Best way to include custom react components between strings/p-tags?

tldr: New to frontend. Trying to include custom components within p-tags for a website, I've tried various methods but can't seem to get it to work unless I hard code the content into the return bit in my react component - this isn't viable as I would like to have many p-tags which would change on my website when a user presses next.
Hi everyone! I'm new to front end programming, and this is my very first question, so please excuse any incorrect terminology and/or question formatting!
I'm currently working on a react project where I have created custom components to include in my webpage. These components work when placed between p-tags.
For example, I made a custom component and it works as expected when I do something like:
function test{
return(
<p>Hello! This is a <ShowDefinition word="website"/> which I made using react! </p>
)}
However, I intend to have lots of content which would change using an incremental index, so I've placed my content in a separate jsx file to store as a dictionary.
I found that when doing something like this:
function test{
return(
<div>{script[index].content}</div>
)};
where
script[index].content = '<p>Hello! This is a <ShowDefinition word="website"/> which I made using react! </p>';
it just shows up as a string literal on the webpage. I've tried to wrap my string in {} but this did not seem to work.
I've also tried dangerouslySetInnerHTML with a dompurification to sanitise the html code
dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(script[index].content)}};
This worked however it excluded all of my custom components. So for the example sentence it would show up on the page as "Hello! This is a which I made using react!"
I understand now this doesn't work because dangerouslySetInnerHTML cannot convert custom components/only accepts html, however I am now at a complete lost as to what to do.
I have thought of storing the content in a md file then parsing it however I have little knowledge of md files/md parsers and from what I've found I don't think solves my problem?
Any advice would be greatly appreciated.
Thank you so much.
Ok, so first of all, this is definitely not how you should think when playing with React. Even if this is technically possible with things like React.createElement or dangerouslySetInnerHTML, I suggest you look at this first. I will help you get the thinking in react.
However if I had to do this in React, I would probably use a custom hooks or any conditional logic to render my jsx.
codesandbox
import "./styles.css";
import React from "react";
const useContentFromIndex = (index) => {
return () => {
if (index === 0) return <p> Index 0 </p>;
if (index === 1) return <p> Index 1 </p>;
return <p> Index 2 </p>;
};
};
export default function App({ index = 0 }) {
const CustomContent = useContentFromIndex(index);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<CustomContent />
</div>
);
}

Adding a html element in a string in react

I know this is not the React way of doing things but it's something i need and i just wanna try a solution.
After an action in my page, i display a JSON with
JSON.stringify(myJSON, null, 4);
This change triggers the render in my class.
Inside this json i have a timestamp value, that i translate into readable time. I do this before stringifying, like this:
myJson.timestamp = `${myJson.timestamp} ( ${newDate(parseInt(myJson.timestamp) * 1000)} )`
Now comes the weird stuff:
I need to have a button right next to this translated timestamp. In the previous version of my app i was doing this easily like this:
myJson.timestamp = myJson.timestamp + "( " + newDate(parseInt(myJson.timestamp) * 1000) + " ) <button>action!</button>"
which rendered my button in the page.
But react just writes it as a string, without rendering it. I guess this happens because ReactDOM doesn't parse that as a new HTML element because render is not triggered anywhere.
I would like to know if it is possible to do something like this in react and how. As you can see it's pretty complicated to try to render the button in that place the react way and i have no idea how to actually do it.
#AnaLizaPandac gave me the proper solution to this problem. dangerouslySetInnerHTML was what i am looking for, but i want to take a moment to go a bit deeper into why this solution is usually wrong and why i actually need it. Here you can find the docs for this property.
As you should know, innerHTML is bad because it exposes your web pages to XSS attacks. Why would i employ this solution though? This page does not contain any shared information with any other user and it doesn't contain any vulnerable inputs. I just needed to decorate my JSON object with a button next to the timestamp, that when clicked, redirects to another public page (like google.com). I believe this behavior doesn't expose my app to XSS and solves my problem in a simple elegant way.
I hope i am now wrong with regard to how dom-based XSS works, in case i misunderstood something, leave a comment.
Edeph,
I may be misdiagnosing the problem you are experiencing since I cannot see the entire React component that is misbehaving.
However, based on what you described, I believe the problem is that you are colocating both JSX (i.e., <button>action!</button>) and javascript code. This is certainly doable and a common usecase for React; however, it would need to be done some way similar to this:
myJson.timestamp = myJson.timestamp + "( " + newDate(parseInt(myJson.timestamp) * 1000) + " )
const MyButton () {
return (
<div>
{myJson.timestamp}
<button>action!</button>
</div>
);
}
The key here is JSX expressions should always be enclosed in parens, and JS expressions colocated with JSX need to be enclosed in curly brackets.
I hope this is helpful.
Rendering components from an array
If you have an array of timestamps, you can render the button component like so:
return myTimestamps.map(timestamp => {
const myButton () {
return (
<div>
{timestamp}
<button>action!</button>
</div>
);
}
}
This will result in a column of timestamps, each with a button.
I would actually like to ask a question in the comments to give you a full answer but I've still not enough reputation to do so.
You have to use the following attribute: dangerouslySetInnerHTML={{__html: {yourHtmlAsString}} like this:
render: function() {
return (
<div className="content" dangerouslySetInnerHTML={{__html: thisIsMyCopy}}></div>
);
}
Here's a working sample:
https://codesandbox.io/s/v8x56yv52l
Having said that, there are ways to circumvent this problem in most cases, unless you return dynamically rendered HTML from your server-side app that can differ in its structure a lot. But if the structure of the HTML is mostly the same and only the content changes, you can dynamically render or omit parts of your JSX like this:
return (
{props.conditionMet ? <div>props.anyContentYouLike</div> : null}
);
You can also build on the above idea and come up with much more complicated solutions:
render () {
innerHTML = props.conditionMet ? <div>props.anyContentYouLike</div> : null};
return innerHTML;
}
Hope that helps.

how to resolve Adjacent JSX elements must be wrapped in an enclosing tag issue in React

i have business and i'm trying to return business array and i'm using map concept for this and i'm trying add new section when index == 3 also i'm trying to get this result without add parent div. please check my code js fiddle demo
const business = [
"business1",
"business2",
"business3",
"business4",
"business5"
]
class Hello extends React.Component {
render() {
return (
<div>
{
business.map((data,index) => {
return index===3 ? <div>New Section</div><div>{data}</div>:<div>{data}</div>
})
}
</div>
)
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
if i add parent parent div i can able to get result but i would like to achieve result without adding parent div when index === 3 because i cannot achieve my design output when adding parent
expected output
business1
business2
business3
newsection
business4
business5
Starting in React 16.2, you can use Fragments. In JSX, you can simply wrap the elements in what looks like an element with an empty name:
<>
Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
</>
~ example from https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html.
If you are using Babel to transpile the code, you may need to use <React.Fragment></React.Fragment> instead of <></>.
As #tech4him explains you can use fragments, if you are not able to run v16.2, you can try v16.0 fragments which will allow you to get something like this:
{
business.map((data,index) => {
return index===3
? [<div key="A">New Section</div>, <div key="B">{data}</div>]
: <div>{data}</div>
})
}
You can find more info about React v16.0 fragments here
The error is caused by returning something to React which is not a single element. This is not allowed (at least React < 16.2). You can either work around it in React, but the problem itself is in the business logic.
You can better solve this outside of React. It seems what you want to do is the following:
If the length of the array is less than 3, do nothing
Otherwise, add an element add position 3, pushing the remainder down
The following render would support that
render() {
const shouldAddElement = companies.length >= 3;
// We make a copy to prevent us from modifying the original, which is not in the class scope
const companyCopy = [...companies];
if(shouldAddElement) {
companyCopy.splice(3, 0, 'newSection');
}
return <div>{totalList}</div>;
}
You can also take a look at How to insert an item into an array at a specific index?

Converting string into HTML Code in React js

I'm getting description from API
description = '<p>Book gor 4 hair cut and get 1 hair cut free validity is ONE year </p>'
And now I'm trying to render the same in HTML
<div>
{parser.parseFromString(description,"text/html").querySelector('body').innerHTML}
</div>
But it's not rendering like html. check my fiddle demo .
The expected output should render the same like html tag behavior.
Well you can use dangerouslySetInnerHTML, but it's not called "dangerous" just for fun...
<div>
dangerouslySetInnerHTML={{__html:
parser
.parseFromString(description,"text/html")
.querySelector('body')
.innerHTML
}}
</div>
Most of the approaches seem to be complicated. The approach I chose to take was, returning the entire function as a JSX element.
For eg:
renderJSXElement(): JSX.Element{
return (Enter html string here)
}
You can pass this as a props to your js render and it should work.
Try like that:
class Hello extends React.Component {
render() {
let description = '<p>Book gor 4 hair cut and get 1 hair cut free validity is ONE year </p>';
return (<div>
<span dangerouslySetInnerHTML={{__html:description}} />
</div>)
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
you should also be able to use Jquery as so
$('.description').html('<p>Book gor 4 hair cut and get 1 hair cut free validity is ONE
year </p>')
https://oscarotero.com/jquery/
I edited this to include parsing the response body. attaching it to the state and rendering state.description in the jsx makes it more dynamic. you can also have the function that calls the api return the parsed response body and call that in your div instead of using state. <div>{this.getDescription()}</div>. worst case scenario, you may have to split the description to remove the p tags and render that in the body. example: description = res.body.split('<p>').join().split('</p>').join(), then handle it either of the ways in my examples. if you remove the p tags you can use jquery to set the text: $('.description').text(description)
constructor(props) {
super(props)
this.state = {
description: '<p>Book gor 4 hair cut and get 1 hair cut free validity is ONE
year </p>'
}
this.getDescription = this.getDescription.bind(this)
}
getDescription() {
apiCall().then(res => {
this.setState({ description : JSON.parse(res.body)})
})
}
render () {
return (
<div className="description">
{this.state.description}
</div>
)
}
}

Categories

Resources