Pass react component inside dangerouslySetInnerHTML - javascript

The server returns something like:
content = <p> Hello world :smile: <strong> NICE </strong> !</p> - this is because we support markdown.
Now I have a parser that parses everything with :{text}: into an emoji. I am using emoji-mart for this one.
So this is what content looks like now:
<p> Hello world ${<Emoji emoji=":smile:" />} <strong> NICE </strong> !</p>
Currently without the emoji parser what we do is:
return React.createElement('div', {
dangerouslySetInnerHTML: {
__html: content,
}
});
However, since we now concatenating the content to contain the Emoji from emoji-mart how will I pass this to dangerouslySetInnerHTML without breaking the markdown?

Upon playing around with the situation I discovered that you can actually pass use functional components and return string instead: https://github.com/missive/emoji-mart#using-with-dangerouslysetinnerhtml (Specific for my problem regarding emoji-mart)
So what I did with my other Components are the same, instead of calling a React component I created a function instead:
function testComponent(props) {
const { style, className, children, html } = props;
if (html) {
return `<span style='${style}' class='${className}'>${children || ''}</span>`;
}
return (
<span style="${style}" class="${className}">
${children || ''}
</span>
);
}
And called it as:
function testComponent(props) {
const { content } = props; // content is a markdown and can be a stringified image tag
return testComponent({ children: content, html: true });
}
And for the dangerouslySetInnerHTML:
(render function inside of your react component)
render() {
const props = {
dangerouslySetInnerHTML: {
__html: testComponent(this.props.content),
},
};
return React.createElement('div', props);
}
This is hackier, but less expensive than using:
renderToString()
renderToStaticMarkup()

You should use React.renderToStaticMarkup(JSXInstance), in your case:
<p> Hello world ${React.renderToStaticMarkup(<Emoji emoji=":smile:" />)} <strong> NICE </strong> !</p>

Related

Passing string literal as a single prop in Typescript + React

It seems this concept is so basic, there's a lack of documentation about it. I can pass objects as props, but can't seem to pass a basic string literal.
Functional Component
I have a functional component that takes a typed prop, like so:
const ChildComponent = (name: string) => {
return (
<div className={styles.childComponent}>
<p className={styles.styledName}>
{ name }
</p>
</div>
);
}
and call it like so:
<ChildComponent name="testName" />
Error
VSCode throws the error on ChildComponent:
Type '{ name: string; }' is not assignable to type 'string'
I'm very new to Typescript, but from what I can tell, it's reading the string literal as an object.
Possible Solutions
Much of what I've read advises creating a custom typed prop, even for a single property, like so:
Type: NameProp {
name: string
}
and using that as the prop type, instead of string.
Isn't this overkill? Or am I missing something very basic.
const ChildComponent = ({ name }: { name: string }) => {
return (
<div className={styles.childComponent}>
<p className={styles.styledName}>{name}</p>
</div>
);
};
You have to destructure it from props object.
props is an object.
CODESADNBOX LINK
const ChildComponent = (props: ChildComponentProps) => {
const { name } = props; // CHANGE THAT YOU HAVE TO DO
return (
<div className={styles.childComponent}>
<p className={styles.styledName}>{name}</p>
</div>
);
};
or
const ChildComponent = ({ name }: ChildComponentProps) => {
return (
<div className={styles.childComponent}>
<p className={styles.styledName}>{name}</p>
</div>
);
};
where ChildComponentProps is
interface ChildComponentProps {
name: string;
}
Define a prop interface the define the props parameter as object :
interface Props{
name:string
}
const ChildComponent: React.FC<Props> = ({name}) => {
return (
<div className={styles.childComponent}>
<p className={styles.styledName}>
{ name }
</p>
</div>
);
}
Props supposed to be an object. You are not the only one using this prop object.
Note that JSX is just a syntax extension use to make it easy to read & code components.
But this code will translate into the pure javascript code,
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
The above code will translate into before execute,
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
Later this createElement will also return an object which will use to build the whole element tree of your application.
// Note: this structure is simplified
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
Notice the children property ? Its also a prop which was not added by you but the React it self.
NOTE: Above code snippets are directly copied from React JS documentation.
So to answer your question,
You cannot change the type of the props object. It must be an object. The reason why you seen that error message is because you forcefully telling the type of props is an string but actually it is not.
The way you should do it is,
const ChildComponent: React.FC<{ name: string }> = ({ name }) => {
return (
<div className={styles.childComponent}>
<p className={styles.styledName}>
{ name }
</p>
</div>
);
}

How do I render components with a switch in Svelte?

I would like to conditionally render components with a switch case statement in Svelte like so:
// index.svelte
<script>
import TextContent from './components/text-content.svelte'
import { someData } from './api/some-data.js'
const ContentSwitch = (data) => {
switch (data._type) {
case 'block':
return data.children.map((child) => ContentSwitch(child));
case 'span':
return (
<TextContent>
<span slot="text-content">{data.text}</span>
</TextContent>
);
}
for (let index = 0; index < data.length; index++) {
return data.map((item) => ContentSwitch(item));
}
};
</script>
<div>
{#each someData as data}
{ContentSwitch(data)}
{/each}
</div>
TextContent component:
// components/text-content.svelte
<slot name="text-content">
<span />
</slot>
It seems that this approach does not work in Svelte as I'm getting an Unexpected Token error.
Is rendering components with a switch possible in Svelte?
What you are writing there resembles more JSX which is for React. In Svelte you do not write HTML in your JavaScript but instead keep those separate.
What you would do is make a lookup table and use svelte:component to render the correct component:
<script>
const BlockTypes = {
"span": TextContent
}
</script>
{#each children as child}
{#if child.type === 'block'}
<svelte:self {...child} />
{:else}
<svelte:component this={BlockTypes[child.type]} {...child} />
{/if}
{/each}
The svelte:self is under the assumptions that this is in itself also an element of type block, for reasons you cannot import a component into itself so you need this special case here. Having this gives you nested blocks out of the box.
In this example you pass all the properties of the child on to the rendered component so you would have to rewrite your components slightly, you could also use slots but that would be a severe mess with named slots.
I think returning the html syntax in the switch 'span' inside the (java)script tag can't work like this.
Actually it's not a switch between different components but rendering differently nested 'data.text' fields all inside a TextContent component?
What's the structure of someData? Assuming it looks something like this
let someData = [
{
_type: 'block',
children: [{
_type: 'span',
text: 'textValue#1'
}]
},
{
_type: 'span',
text: 'textValue#2'
}
]
A recursive function could be used to get all nested text fields
function getSpanTexts(dataArr) {
return dataArr.flatMap(data => {
switch (data._type) {
case 'block':
return getSpanTexts(data.children)
case 'span':
return data.text
}
})
}
$: textArr = getSpanTexts(someData) // ['textValue#1', 'textValue#2']
The text fields then can be iterated with an each loop inside the html, each rendered inside a TextContent component
<div>
{#each textArr as text}
<TextContent>
<span slot="text-content">{text}</span>
</TextContent>
{/each}
</div>
See this working REPL of the code snippets

Can someone provide a "hello world" example of JSX being used with out React?

I keep hearing that the two are not tied together, that you can compile JSX to JavaScript with out React but I have never seen it.
For example:
function Welcome() {
return <h1>Hello, World</h1>;
}
If I feed this to babel compiler I get:
function Welcome() {
return /*#__PURE__*/React.createElement("h1", null, "Hello, World");
}
which requires the React library.
You can see this online here:
https://babeljs.io/repl/
Can someone provide a Hello World example of JSX being used without React?
Here's a good resource. All you need is to write a createElement function, to replace React.createElement with.
// Setup some data
const name = 'Geoff'
const friends = ['Sarah', 'James', 'Hercule']
// Create some dom elements
const app = (
<div className="app">
<h1 className="title"> Hello, world! </h1>
<p> Welcome back, {name} </p>
<p>
<strong>Your friends are:</strong>
</p>
<ul>
{friends.map(name => (
<li>{name}</li>
))}
</ul>
</div>
)
// Render our dom elements
window.document.getElementById('app').replaceWith(app)
<div id='app'></div>
<script>
window.React = {
// A jsx pragma method to create html dom elements (more info below)
createElement(tagName, attrs = {}, ...children) {
const elem = Object.assign(document.createElement(tagName), attrs)
for (const child of children) {
if (Array.isArray(child)) elem.append(...child)
else elem.append(child)
}
return elem
}
};
</script>
The above is using the client-side Babel transpiler, which automatically transforms JSX syntax into references to React.createElement - but instead of importing the React library, a custom window.React is defined, with your custom createElement function.
For pre-compiled projects, you need to set your Babel config to
{
"plugins": [
["#babel/plugin-transform-react-jsx", { "pragma": "createElement" }]
],
"comments": false
}
with a global createElement function.

How to render HTML string as real HTML?

Here's what I tried and how it goes wrong.
This works:
<div dangerouslySetInnerHTML={{ __html: "<h1>Hi there!</h1>" }} />
This doesn't:
<div dangerouslySetInnerHTML={{ __html: this.props.match.description }} />
The description property is just a normal string of HTML content. However it's rendered as a string, not as HTML for some reason.
Any suggestions?
Is this.props.match.description a string or an object? If it's a string, it should be converted to HTML just fine. Example:
class App extends React.Component {
constructor() {
super();
this.state = {
description: '<h1 style="color:red;">something</h1>'
}
}
render() {
return (
<div dangerouslySetInnerHTML={{ __html: this.state.description }} />
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Result: http://codepen.io/ilanus/pen/QKgoLA?editors=1011
However if description is <h1 style="color:red;">something</h1> without the quotes '', you're going to get:
​Object {
$$typeof: [object Symbol] {},
_owner: null,
key: null,
props: Object {
children: "something",
style: "color:red;"
},
ref: null,
type: "h1"
}
If It's a string and you don't see any HTML markup the only problem I see is wrong markup..
UPDATE
If you are dealing with HTML Entities, You need to decode them before sending them to dangerouslySetInnerHTML that's why it's called "dangerously" :)
Working example:
class App extends React.Component {
constructor() {
super();
this.state = {
description: '<p><strong>Our Opportunity:</strong></p>'
}
}
htmlDecode(input){
var e = document.createElement('div');
e.innerHTML = input;
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
render() {
return (
<div dangerouslySetInnerHTML={{ __html: this.htmlDecode(this.state.description) }} />
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
I use 'react-html-parser'
yarn add react-html-parser
import ReactHtmlParser from 'react-html-parser';
<div> { ReactHtmlParser (html_string) } </div>
Source on npmjs.com
Lifting up #okram's comment for more visibility:
from its github description: Converts HTML strings directly into React
components avoiding the need to use dangerouslySetInnerHTML from
npmjs.com A utility for converting HTML strings into React components.
Avoids the use of dangerouslySetInnerHTML and converts standard HTML
elements, attributes and inline styles into their React equivalents.
Check if the text you're trying to append to the node is not escaped like this:
var prop = {
match: {
description: '<h1>Hi there!</h1>'
}
};
Instead of this:
var prop = {
match: {
description: '<h1>Hi there!</h1>'
}
};
if is escaped you should convert it from your server-side.
The node is text because is escaped
The node is a dom node because isn't escaped
If you have HTML in a string I would recommend using a package called html-react-parser.
Installation
NPM:
npm install html-react-parser
yarn:
yarn add html-react-parser
Usage
import parse from 'html-react-parser'
const yourHtmlString = '<h1>Hello</h1>'
code:
<div>
{parse(yourHtmlString)}
</div>
If you have control over where the string containing html is coming from (ie. somewhere in your app), you can benefit from the new <Fragment> API, doing something like:
import React, {Fragment} from 'react'
const stringsSomeWithHtml = {
testOne: (
<Fragment>
Some text <strong>wrapped with strong</strong>
</Fragment>
),
testTwo: `This is just a plain string, but it'll print fine too`,
}
...
render() {
return <div>{stringsSomeWithHtml[prop.key]}</div>
}
I use innerHTML together a ref to span:
import React, { useRef, useEffect, useState } from 'react';
export default function Sample() {
const spanRef = useRef<HTMLSpanElement>(null);
const [someHTML,] = useState("some <b>bold</b>");
useEffect(() => {
if (spanRef.current) {
spanRef.current.innerHTML = someHTML;
}
}, [spanRef.current, someHTML]);
return <div>
my custom text follows<br />
<span ref={spanRef} />
</div>
}
UPDATE:
I removed someHTML state and added comments to make the example more coincise around the concept.
/**
* example how to retrieve a reference to an html object
*/
import React, { useRef, useEffect } from 'react';
/**
* this component can be used into another for example <Sample/>
*/
export default function Sample() {
/**
* 1) spanRef is now a React.RefObject<HTMLSpanElement>
* initially created with null value
*/
const spanRef = useRef<HTMLSpanElement>(null);
/**
* 2) later, when spanRef changes because html span element with ref attribute,
* follow useEffect hook will triggered because of dependent [spanRef].
* in an if ( spanRef.current ) that states if spanRef assigned to valid html obj
* we do what we need : in this case through current.innerHTML
*/
useEffect(() => {
if (spanRef.current) {
spanRef.current.innerHTML = "some <b>bold</b>";
}
}, [spanRef]);
return <div>
my custom text follows<br />
{/* ref={spanRef] will update the React.RefObject `spanRef` when html obj ready */}
<span ref={spanRef} />
</div>
}
You just use dangerouslySetInnerHTML method of React
<div dangerouslySetInnerHTML={{ __html: htmlString }} />
Or you can implement more with this easy way: Render the HTML raw in React app
In my case, I used react-render-html
First install the package by npm i --save react-render-html
then,
import renderHTML from 'react-render-html';
renderHTML("<a class='github' href='https://github.com'><b>GitHub</b></a>")
I could not get npm build to work with react-html-parser. However, in my case, I was able to successfully make use of https://reactjs.org/docs/fragments.html. I had a requirement to show few html unicode characters , but they should not be directly embedded in the JSX. Within the JSX, it had to be picked from the Component's state. Component code snippet is given below :
constructor()
{
this.state = {
rankMap : {"5" : <Fragment>★ ★ ★ ★ ★</Fragment> ,
"4" : <Fragment>★ ★ ★ ★ ☆</Fragment>,
"3" : <Fragment>★ ★ ★ ☆ ☆</Fragment> ,
"2" : <Fragment>★ ★ ☆ ☆ ☆</Fragment>,
"1" : <Fragment>★ ☆ ☆ ☆ ☆</Fragment>}
};
}
render()
{
return (<div class="card-footer">
<small class="text-muted">{ this.state.rankMap["5"] }</small>
</div>);
}
i use https://www.npmjs.com/package/html-to-react
const HtmlToReactParser = require('html-to-react').Parser;
let htmlInput = html.template;
let htmlToReactParser = new HtmlToReactParser();
let reactElement = htmlToReactParser.parse(htmlInput);
return(<div>{reactElement}</div>)
You can also use parseReactHTMLComponent from Jumper Package. Just look at it, it's easy and you don't need to use JSX syntax.
https://codesandbox.io/s/jumper-module-react-simple-parser-3b8c9?file=/src/App.js .
More on Jumper:
https://github.com/Grano22/jumper/blob/master/components.js
NPM Package:
https://www.npmjs.com/package/jumper_react
// For typescript
import parse, { HTMLReactParserOptions } from "html-react-parser";
import { Element } from "domhandler/lib/node";
export function contentHandler(postContent: string) {
const options: HTMLReactParserOptions = {
replace: (domNode: Element) => {
if (domNode.attribs) {
if (domNode.attribs.id === 'shortcode') {
return <div className="leadform">Shortcode</div>;
}
}
},
};
return parse(postContent, options);
}
// Usage: contentHandler("<span>Hello World!</span>")
If you have control to the {this.props.match.description} and if you are using JSX. I would recommend not to use "dangerouslySetInnerHTML".
// In JSX, you can define a html object rather than a string to contain raw HTML
let description = <h1>Hi there!</h1>;
// Here is how you print
return (
{description}
);

escaping html tags in meteor react

I have the following react component
homeComponent = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
homeData: homeContent.findOne()
}
},
render(){
let homeData = this.data.homeData;
return (
<div className="units-container home-container">
<h1>ply</h1>
<p>{homeData.homeCopy}</p>
Download
</div>
)
}
})
The key {homeData.homeCopy} returns a string that has html tags in. So in this case let's say it returns <strong>bold text</strong> not bold text.
I'm using react-template-helper to use both blaze and react, so I would expect that I could use triple brackets to escape the html, but that's not working. I need to escape the html from react I suppose.
How can I get react to honor these html tags from the string?
#mason505 pointed this in the right direction, but I think this needs some extra information.
First you must make a function to parse the html:
function escapeHTML(data) {
return {__html: data}
}
Then you must call that with dangerouslySetInnerHTML but only on a self enclosed html element and you must pass the data into dangerouslySetInnerHTML using the escapeHTML function. Complete example is:
function escapeHTML(data) {
return {__html: data}
}
homeComponent = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
homeData: homeContent.findOne()
}
},
render(){
let homeData = this.data.homeData;
return (
<div className="units-container home-container">
<h1>ply</h1>
<p dangerouslySetInnerHTML={escapeHTML(homeData.homeCopy)} />
Download
</div>
)
}
})

Categories