Adding a html element in a string in react - javascript

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.

Related

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>
);
}

Set a default component for Html Elements in React

I've been playing with reactjs a bit lately. As I understand, ReactJS will convert html elements into something like this:
<div><span>Text</span></div>
Into
React.createElement('div', null, React.createElement('span', null, 'Text'))
So far so good, but what I'd like to do is have special component that will override all html elements so some kind of basic behavior can be added to any html element using props.
So instead of the above, I'd want something like this instead:
React.createElement(Html('div'), null, React.createElement(Html('span'), null, 'Text'))
This would make it possible to make usual html a bit more dynamic without having to define a new component each time. One alternative in my mind to this would be to have Component based on the props. So adding a particular prop would give a particular trait to the component above.
One example would be to have something like this:
<div foreach="collection" as="value">
<div my-value="value" />
</div>
Just to explain a bit further, the idea is mainly to adapt React to different templating engines. So it's not necessary to rewrite completely templates to work in React and gives some kind of compatibility layer between a legacy templating engine.
So rewriting as this isn't what I'm asking. I already know how to write directly for React.
<div>
{
this.collection.map((value) => {
return <div>{value}</div>
})
}
</div>
One alternative for me which would be quite easy if React can't do that easily is to transform the html elements as Uppercase or differently so React thinks it's a component instead of html.

Render custom React component within HTML string from server

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>

Re-transpile JSX on the fly in a reactjs app

Is it possible to access and modify the JSX used to create a React class and re-transpile it on the fly. For example, if you had the following:
var Item = React.createClass({
render: function () {
return <div>Hello</div>
}
}
How could we: access the raw JSX, modify it to return something else, then transpile so the changes could be seen?
YES I understand this could be extremely dangerous.
It's probably easier to compile the JSX to JS first, and then use something like esprima to make the changes you need on the AST esprima gives you. Then pretty print it to JS again.
But I have to ask what your use case is, because it doesn't seem like the very best of ideas.

How to output text in ReactJS without wrapping it in span

In my ReactJS-based application I do:
var _ = React.DOM;
_.span(null, 'some text', _.select(null, ...));
The problem is: 'some text' is wrapped in additional span element in the DOM. Is there any way to avoid this behavior and just output raw text?
To be clear: I want to output
<span>some text<select>...</select></span>
not
<span><span>some text</span><select>...</select></span>
Update: This is now "fixed" in React v15 (2016-04-06) – now comment nodes are added around each piece of text, but it is no longer wrapped in a <span> tag.
We received some amazing contributions from the community in this release, and we would like to highlight this pull request by Michael Wiencek in particular. Thanks to Michael’s work, React 15 no longer emits extra <span> nodes around the text, making the DOM output much cleaner. This was a longstanding annoyance for React users so it’s exciting to accept this as an outside contribution.
Full release notes.
This is currently a technical limitation of React; it wraps any floating text nodes in a span so that it can assign it an ID and refer back to it later. In a future version of React hopefully we can remove this restriction.
You can hard code the html as a last resort.
<option value={value} dangerouslySetInnerHTML={{__html: value}}></option>
Well.. If you're hell bent on doing this, and accept the limitation that you cannot access props or state, you could do this:
var Component = React.createClass({
displayName:"Statics",
statics: {
customRender: function(foo) {
return React.renderToStaticMarkup(<div
dangerouslySetInnerHTML={{__html: foo.bar }}/>);
}
},
render: function () {
return <div dangerouslySetInnerHTML={{__html:
<Component.customRender bar="<h1>This is rendered
with renderToStaticMarkup</h1>" />}} />
}
});
renderToStaticMarkup will not insert any spans or react-dataid, and is meant for static server rendering. It's probably not a great idea to do this, but there you go.
renderToStaticMarkup
Similar to renderToString, except this doesn't create extra DOM
attributes such as data-react-id, that React uses internally. This is
useful if you want to use React as a simple static page generator, as
stripping away the extra attributes can save lots of bytes.
Check the result at: http://learnreact.robbestad.com/#/static
I Changed the version of react and react-dom and worked perfect
"react": "^15.0.1",
"react-dom": "^15.0.1"

Categories

Resources