Render HTML string w/JSX expression in React dangerouslySetInnerHTML() - javascript

I have successfully used React's dangerouslySetInnerHTML to render HTML string responses I'm getting from an API, but now I need to add a click handler to parts of certain responses that are set by dangerouslySetInnerHTML. Is there a way to add handlers to dangerouslySetInnerHTML conent? Example:
var api_response = '<a onClick={this.props.methodName} href="www.google.com">example</a>';
return <div dangerouslySetInnerHTML={_markupFromString(api_response)}></div>
function _markupFromString (msg) {
return { '__html': msg };
}

This isn't going to work. dangerouslySetInnerHTML() doesn't work with JSX. A better strategy would be to have the AJAX call simply return the required strings and (link and title) and supply a component with these values as props. The component is simply the link in this situation.

Or you can add your SVG in the file public/index.html then create In your component a function like this
doSomething() {
console.log('ajajaj');
}
componentDidMount() {
window.NameVarible = this.doSomething;
}
And add your event onClick="NameVariable()" in the SVG

Related

Setting an attribute dynamically in mithril.js

I'd like to set an attribute in mithril.js inside the oninit function. For example,
m('img', {
oninit: (vnode) => {
vnode.attrs.src = 'A url string';
}
})
I want to check for certain conditions before setting this attribute and I'd rather not use the ternary operator because that would get messy. The above example half worked, on logging vnode, the attrs object has the correct src string, however in the DOM, src is set to null for some reason.
I'm looking for a simple solution and not something like, give an id to the element and use it to change the attribute.
Usually such code could be placed in the component as a method or as a function when using a closure component.
This is in a component just for an image but such work could be done in a parent component that rendered that IMG directly.
function DynamicImg() {
function imageAttrs(vnode) {
let src = 'configurable';
return {src: src};
}
return {
view: function (vnode) {
return m('img', imageAttrs(vnode));
}
}
}
Setting an attr in init would not last as the view function would be called without it immediately. Since it is called at every render.
For anyone looking for a solution, this worked for me.
m('img', {
src: (() => {
return 'A url string';
})()
})
You can define the function elsewhere and call it as well.

ReactJS - render external DOM element with events

I use ReactJS 16.13.1 and now I want to render an external DOM element with its events.
So let's assume there is a
<button type="button" id="testBtnSiri" onclick="alert('Functionality exists');">Testbutton Siri</button>
Which has been generated by a library (with some other events as well).
Now I want:
To copy it (with events) & render this element in my ReactJS render function.
What is the most appropriate way to do this ?
I am not looking for ReactDOM.createPortal().
I want to show an external DOM element with its events in my React Component.
In order to use HTML within your React code you need to make use of dangerouslySetInnelHTML prop on an element.
function libraryReturnedHTML() {
return '<button type="button" id="testBtnSiri" onclick="alert('Functionality exists');">Testbutton Siri</button>'
}
function MyComponent() {
return <div dangerouslySetInnerHTML={__html: libraryReturnedHTML} />;
}
However you must know that this makes your code susceptible to XSS attacks. In order to prevent such scenarios its a good idea to first sanitize your HTML so that you have only the desired scripts and link tags. In order to sanitizehtml you can simply use normal Javascript functions and document.createTreeWalker
You can also make use of an existing library to sanitize your HTML. For example sanitize-html
However the sanitization is just an added precaution.
Create a wrapper over Node.addEventListener() which will catch all the event listeners added to the button when it's rendered by other library. It could look like this(this code have to be executed before the external library creates the button):
let ael = Node.prototype.addEventListener
let eventListeners = []
Node.prototype.addEventListener = function(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void {
if((this as HTMLElement).id === "buttonID")
eventListeners.push({type, listener, options})
this.addEventListener = ael
this.addEventListener(type, listener, options)
}
Create react component with your button - you can create a new one, or inject using dangerouslySetInnerHtml. Pass eventListeners array as a prop. On component mount and unmount, add/remove the event listeners to your button.
function ClonedButton({eventListeners}: any){
useEffect(()=>{
eventListeners.forEach((el) => document.getElementById("my-migrated-new-button")!.addEventListener(el.type, el.listener, el.options));
return () => {
eventListeners.forEach((el) => document.getElementById("my-migrated-new-button")!.removeEventListener(el.type, el.listener, el.options));
}
}, [])
return <button id="my-migrated-new-button"></button>
}

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>

How do I render a string as children in a React component?

Take a simple component:
function MyComponent({ children }) {
return children;
}
This works:
ReactDOM.render(<MyComponent><span>Hello</span></MyComponent>, document.getElementById('stage'));
but this doesn't (I removed the <span/>):
ReactDOM.render(<MyComponent>Hello</MyComponent>, document.getElementById('stage'));
because React tries to call render on the string:
Uncaught TypeError: inst.render is not a function
On the other hand, this works fine:
ReactDOM.render(<p>Hello</p>, document.getElementById('stage'));
How do I make <MyComponent/> behave like <p/>?
If you're using React 16.2 or higher, you can do this using React fragments:
const MyComponent = ({children}) => <>{children}</>
If your editor doesn't support fragment syntax, this will also work:
const MyComponent = ({children}) =>
<React.Fragment>{children}</React.Fragment>
Keep in mind that you're still creating & returning a component (of type MyComponent) as far as React is concerned - it just doesn't create an additional DOM tag. You'll still see a <MyComponent> tag in the React Debug Tools, and MyComponent's return type is still a React element (React.ReactElement).
Well the difference is <p> is an html element and MyComponent is a React Component.
React components need to render/return either a single component or a single html element.
'Hello' is neither.
You need at least one top-level HTML element. Your component can't really just output a string, that's not how React works.
The simplest solution is to simply make your MyComponent wrap it's output in a span or div.
function MyComponent({ children }) {
return <span>{ children }</span>;
}
Currently, in a component's render, you can only return one node; if
you have, say, a list of divs to return, you must wrap your components
within a div, span or any other component.
source
And what you are returning is not a root node. You are returning a react component that is returning a string where it should be returning an HTML element.
You can either pass your string already wrapped with an HTML element (like you already did in your example) or you can wrap your string in a HTML element inside your "MyComponent" like this
function MyComponent({ children }) {
return <span>{ children }</span>;
}
React can render either React components (classes) or HTML Tags (strings). Any HTML tag is by convention lowercase where a Component is Capitalized. Every React component has to render exactly one Tag (or null). To answer your question: you cannot.
In the example above, you render what's given with the children attribute where this will render the tag inside or a string that is not valid.

How I can render react components without jsx format?

I try to make my "smart" popup component, which can open inside itself some components, but my realization isn't good, because it doesn't work.
I use redux approach for creating popup and action of opening my popup is able to get name of any component for rendering before popup will be open;
But I've some problem, after getting parameter, in our case it's nameOfComponent, I need to choose and render component with name nameOfComponent.
And my question now, how do It can render component from array?
// He's my components
import Login from '../Login/login.js';
import Logout from '../Logout/logout.js';
const popupContent = {
Login : Login,
logout: Logout
};
// My component
class Popup extends Component {
componentDidUpdate () {
// for example
const nameOfComponent = "Login";
this.body = this.setBodyPopup(nameOfComponent);
return true;
}
setBodyPopup(property){
return popupContent[property];
}
render() {
// I want to render some element from popupContent here
<div>
// <this.body /> // it's jsx format
{this.body}
</div>
}
}
I added working example here JSfiddle ReactJS
You dont have to use JSX. If you do, right way to do this is to use factory. You can also render regular HTML in the render method, as well as to use vanilla javascript in your code using curly braces.
Also to get I would recommend mapping and iterating through all your items in array and render them one by one in the render method
see example below:
var Login = React.createClass({
render: function() {
return <div>{this.props.name}, logged in</div>;
}
});
// React with JSX
ReactDOM.render(<Login name="John" />,
document.getElementById('containerJSX'));
// React without JSX
var Login = React.createFactory(Login);
ReactDOM.render(Login({ name: 'Tim' }),
document.getElementById('containerNoJSX'));
As commentators suggest, you might want to specify this.body either in constructor or within the render method itself.
However, if I understand your intention correctly, you could just use this.props.children instead.
E.g.
<Popup><MyInnerComponent></Popup>
and in Popup render method
render() {
<div>
{this.props.children}
</div>
}
React actually allows you to use variables with JSX syntax to instantiate components. You should actually be able to call <this.body /> and have it work; however yours will not because you do not define this.body until the componentDidUpdate method, which means it will be undefined for the first render and break everything. I would suggest using local component state for this instead of this.body and making sure it is defined from the start.
At the very least, instantiate this.body in a constructor to some value:
constructor(props) {
super(props);
this.body = 'Login';
}
You can use <this.body /> to render the component, as long as this.body has an actual value. Perhaps you just need:
render() {
return <div>
{this.body ? <this.body /> : null}
</div>
}
With the example you gave, though, you can just put the contents of your componentDidMount in the constructor instead, because the constructor is invoked before the first render pass.
I think you are looking at something like dangerouslySetInnerHtml.
<div dangerouslySetInnerHTML={this.body} />

Categories

Resources