React props: Using an HTML entity within JSX dynamic content? - javascript

I have a React component, to whose props I want to assign a string that includes both JavaScript variables and HTML entities.
Some of the approaches I've attempted have resulted in the HTML entity being rendered escaped. For example, – gets rendered literally as "–" instead of as "–".
Is there a way to get an HTML entity to render unescaped in a JSX dynamic content block being assigned to a React props?
Attempts Made
Tried using a template literal:
<MyPanel title={`${name} – ${description}`}> ... </MyPanel>
Problem: In the rendered output, the – is being rendered literally as "–" instead of as "–".
Attempted to construct some simple JSX with no quotes:
<MyPanel title={{name} – {description}} ... </MyPanel>
Problem: This failed at compile time with a syntax error.
Tried working around the syntax error by wrapping the JSX in a <span /> element:
<MyPanel title={<span>{name} – {description}</span>} ... </MyPanel>
Problem: This works, but I'd rather avoid the superfluous <span /> element being present in the rendered output.
Tried replacing the HTML entity with a Unicode numeric character reference:
<MyPanel title={name + ' \u2013 ' + description} ... </MyPanel>
Problems:
This works, but (in my opinion) makes the code a little less
readable. (It's more obvious that "ndash" rather than "2013"
represents an en-dash character.)
Also, this involves +-operator concatenation, which triggers a Unexpected string concatenation prefer-template error in my team's JSLint checker; a solution that uses string interpolation instead would be better.

You can avoid the superfluous span with a Fragment:
<MyPanel title={<>{name} – {description}</>} ... </MyPanel>
This feature was introduced in React 16.2.
See the Documentation
I agree with #samanime that using the actual character is best for simple cases, but if your content is truly dynamic, I would prefer using a Fragment over either the entityToChar or dangerouslySetInnerHTML approaches.

Here are a few options (I outlined these in a more general answer awhile back):
Easiest - Use Unicode
<MyPanel title={ `${name} – ${description}` } />
Safer - Use the Unicode number for the entity inside a Javascript string.
<MyPanel title={`${name} \u2013 ${description}`} />
or
<MyPanel title={`${name} ${String.fromCharCode(8211)} ${description}`} />
Last Resort - Insert raw HTML using dangerouslySetInnerHTML.
title={`${name} – ${description}`}
with:
<div dangerouslySetInnerHTML={{__html: props.title}}></div>
const MyPanel = (props) => {
return (
<div>{props.title}</div>
)
}
const MyPanelwithDangerousHTML = (props) => {
return (
<div dangerouslySetInnerHTML={{__html: props.title}}></div>
)
}
var description = "description";
var name = "name";
ReactDOM.render(<MyPanel title={`${name} – ${description}`} />
, document.getElementById("option1"));
ReactDOM.render(<MyPanel title={`${name} \u2013 ${description}`} />
, document.getElementById("option2"));
ReactDOM.render(<MyPanel title={`${name} ${String.fromCharCode(8211)} ${description}`} />
, document.getElementById("option3"));
ReactDOM.render(<MyPanelwithDangerousHTML title={`${name} – ${description}`} />
, document.getElementById("option4"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="option1"></div>
<div id="option2"></div>
<div id="option3"></div>
<div id="option4"></div>

Here is React's documentation on HTML entities: JSX Gotchas
Of those, using the actual character instead of the HTML entity would be the best:
<MyPanel title={ `${name} – ${description}` } />
If you can't do that because the HTML entity is dynamic (it's not just a hard-coded en-dash), you could translate the entity. Here is a little function that can do that:
const entityToChar = str => {
const textarea = document.createElement('textarea');
textarea.innerHTML = str;
return textarea.value;
}
You then use it like this:
<MyPanel title={ entityToChar(`${name} – ${description}`) } />

Without knowing how <MyPanel /> works, I can only speculate that you could do something like the following:
<MyPanel title={`${name} – ${description}`}> ... </MyPanel>
MyPanel.js
render() {
const { title } = this.props;
return <div dangerouslySetInnerHTML={{ __html: title }} />;
}

Since you probably don't want to allow arbitrary URL in your title prop, I'd be tempted to write myself a function that only handles turning character entities into their Unicode character equivalent. Sort of "HTML-lite." :-) There aren't that many named references, really; and the numeric ones are easy:
const named = {
"ndash": "–", // or "\u2013"
"mdash": "—", // or "\u2014"
"nbsp": " " // or "\u00A0"
// ...
};
// Obviously this is a SKETCH, not production code!
function convertCharEntities(str) {
return str.replace(/&([^ ;&]+);/g, (_, ref) => {
let ch;
if (ref[0] === "#") {
let num;
if (ref[0].toLowerCase() === "x") {
num = parseInt(ref.substring(2), 16);
} else {
num = parseInt(ref, 10);
}
ch = String.fromCodePoint(num);
} else {
ch = named[ref.toLowerCase()];
}
return ch || "";
});
}
Then use it when rendering that prop:
class Example extends React.Component {
render() {
return <div>{convertCharEntities(this.props.title || "")}</div>;
}
}
Full Live Example:
const named = {
"ndash": "–", // or "\u2013"
"mdash": "—", // or "\u2014"
"nbsp": " " // or "\u00A0"
// ...
};
// Obviously this is a SKETCH, not production code!
function convertCharEntities(str) {
return str.replace(/&([^ ;&]+);/g, (_, ref) => {
let ch;
if (ref[0] === "#") {
let num;
if (ref[0].toLowerCase() === "x") {
num = parseInt(ref.substring(2), 16);
} else {
num = parseInt(ref, 10);
}
ch = String.fromCodePoint(num);
} else {
ch = named[ref.toLowerCase()];
}
return ch || "";
});
}
class Example extends React.Component {
render() {
return <div>{convertCharEntities(this.props.title || "")}</div>;
}
}
ReactDOM.render(
<Example title="Testing 1 2 3 — enh, you know the drill <script src='nefarious.js'><\/script>" />,
document.getElementById("root")
);
<div id="root"></div><script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Note that the tags were not output as tags, but the entities were handled.

Related

How to replace text with substitute that contains JSX code?

interface ICard {
content: string,
blanks: Array<{word: string, hidden: boolean}>
}
function processCards():Array<any>{
if (cards !==null ){
const text = cards.map((card,cardIndex)=>{
var content = card.content
card.blanks.map((blank,blankIndex)=>{
// replace content
const visibility = (blank.hidden)?'hidden':'visible'
const click_blank = <span className={visibility} onClick={()=>toggleBlank(cardIndex,blankIndex)}>{blank.word}</span>
content = content.replace(blank.word,click_blank)
})
return content
})
return text
} else {
return []
}
}
I have an array of objects of type ICard.
Whenever card.blanks.word appears in card.content, I want to wrap that word in tags that contain a className style AND an onClick parameter.
It seems like I can't just replace the string using content.replace like I've tried, as replace() does not like the fact I have JSX in the code.
Is there another way to approach this problem?
You need to construct a new ReactElement from the parts of string preceding and following each blank.word, with the new span stuck in the middle. You can do this by iteratively building an array and then returning it wrapped in <> (<React.Fragment>). Here's a (javascript) example:
export default function App() {
const toggleBlankPlaceholder = (cardIndex, blankIndex) => {};
const cardIndexPlaceholder = 0;
const blanks = [
{ word: "foo", hidden: true },
{ word: "bar", hidden: false },
];
const content = "hello foo from bar!";
const res = [content];
for (const [blankIndex, { word, hidden }] of blanks.entries()) {
const re = new RegExp(`(.*?)${word}(.*)`);
const match = res[res.length - 1].match(re);
if (match) {
const [, prefix, suffix] = match;
res[res.length - 1] = prefix;
const visibility = hidden ? "hidden" : "visible";
res.push(
<span
className={visibility}
onClick={() =>
toggleBlankPlaceholder(cardIndexPlaceholder, blankIndex)
}
>
{word}
</span>
);
res.push(suffix);
}
}
return <>{res}</>;
}
The returned value will be hello <span class="hidden">foo</span> from <span class="visible">bar</span>!
A couple of things:
In your example, you used map over card.blanks without consuming the value. Please don't do that! If you don't intend to use the new array that map creates, use forEach instead.
In my example, I assumed for simplicity that each entry in blanks occurs 0 or 1 times in order in content. Your usage of replace in your example code would only have replaced the first occurrence of blank.word (see the docs), though I'm not sure that's what you intended. Your code did not make an ordering assumption, so you'll need to rework my example code a little depending on the desired behavior.

fat arrow function with a ternary working with parentheses instead of curly braces!!! Why?

import React from "react";
import Hill from "./Hill";
import Animal from "./Animal";
const fav = "hill";
this is not working;
// const App = () => {
// <>
// <h1> my fav card</h1>;
// {fav === "hill" ? <Hill/> : <Animal/>}
// </>
// }
this code is working when i am using parentheses insted of
curly braces...why???
const App = () => (
<>
<h1> my fav card</h1>;
{fav === "hill" ? <Hill /> : <Animal />}
</>
);
export default App;
Arrow functions can have either an expression, or a function body, like:
const five = () => 5;
or
const five = () => {
return 5;
}
Note that the second needs to use return to return its return value.
Your code with braces will work if you use the return statement to return your JSX.
Arrow functions can implicitly return the value that comes after the fat arrow (in your case the value is what is in the parens).
If you use brackets, you’ll need to explicitly return a value using the “return” keyword (like a regular function definition).
If you want to use brackets (benefit being if you want to add some logic outside of the return statement in the future without needing to add brackets later), just wrap the current code in the brackets with parens and put a “return” in front of it. Otherwise, using the arrow function as you have it works perfectly well - up to you.
Note that the ternary has no relation to this question.

React add HTML tags on string using predetermined components (alternative to dangerouslySetInnerHTML )

I do not want to employ dangerouslySetInnerHTML and the objective here is to put bold tags around each instance of a word that appears in my string.
Convention React wisdom might suggest that something like this could work?
const Bold = (props) => {
return (
<b>
{props.txt}
</b>
);
};
Here is where I try to incorporate the code
if (longString.includes(searchTerm)) {
longString = longString.replace(rest, <Bold txt={rest} />);
}
Problem is it comes out as [object Object] instead of desired <b>searchTerm</b>
How do I do set up the string swap so that it doesn't print the word [object] but rather prints the object?
You could try renderToString from react-dom
import { renderToString } from 'react-dom/server'
if (longString.includes(searchTerm)) {
longString = longString.replace(rest, renderToString(<Bold txt={rest} />) );
}

Replace subsring by an HTML tag in react

How can i surround certain letters in a string by an html tag?
For example, given this string: Sgt. Pepper's Lonely Hearts Club Band and this substring: Pepp. I want to surround the substring inside the string with an html tag. Like this: Sgt. <mark class="Search-suggestions-match">Pepp</mark>er's Lonely Hearts Club Band.
I achieved this, but React is escaping the span tags. But also, i don't know if i should take another approach, maybe a more JSX one.
This is the component where im trying to implement it:
class SearchSuggestions extends Component {
constructor(props) {
super(props)
if (!this.props.suggestions || !this.props.term) {
return
}
this.state = {
suggestions : this.props.suggestions.map(item => this.markText(item))
}
}
markText(string) {
return string.replace(new RegExp(this.props.term, "ig"), match => {
return `<mark class="Search-suggestions-match">${match}</mark>`
})
}
render() {
if (!this.props.suggestions || !this.props.term) {
return null
}
return (
<ul className="Search-suggestions-component">
{this.state.suggestions.map((value, i) => <li key={i}>{value}</li>)}
</ul>
)
}
}
Use a regular expression to split the string, capturing the desired match, and then format it with JSX:
markText(string) {
let strArr = string.split(new RegExp(`(${this.props.term})`, "ig"));
return strArr.map((ea, i) => {
if(ea.toLowerCase() === this.props.term.toLowerCase()){
return <mark key={`match${i}`} className="Search-suggestions-match">{ea}</mark>
} else {
return ea;
}
});
}
HTML inside of a string will NOT get added to the DOM, so you need to use JSX to return an actual React element instead.
Edit: added toLowerCase() so that matches will ignore letter case like they do in the regular expression.
Typically you should not pass JSX as a string and expect React to render the element.
However there is an escape hatch that can be used to achieve what you want, check out dangerouslySetInnerHTML
From the docs:
dangerouslySetInnerHTML is React's replacement for using innerHTML
in the browser DOM. In general, setting HTML from code is risky
because it's easy to inadvertently expose your users to a cross-site
scripting (XSS) attack. So, you can set HTML directly from React, but
you have to type out dangerouslySetInnerHTML and pass an object with a
__html key, to remind yourself that it's dangerous
So you could still do something like this:
render() {
...
return (
<ul className="Search-suggestions-component">
{
this.state.suggestions.map((value, i) => (
<li key={i} dangerouslySetInnerHTML={{ __html: value }} />
)
}
</ul>
)
}

Rendering custom html tag with react.js

What I'm trying to do is quite easy at first however I get an (obviously completely useless) error from webpack and I'm wondering how it can be fixed, I want a simple "custom" tag to be rendered by React, the code is as follows:
let htmlTag = "h" + ele.title.importance;
let htmlTagEnd = "/h" + ele.title.importance;
return(
<{htmlTag} key={elementNumber}>{ele.title.content}<{htmlTagEnd}>
);
Basically instead of having a predefined tag I want to have my own {template} tag, I know in this situation there would be work arounds for this (e.g. defining a className with my "importance" value and adding some css for that), but for the sake of science I'd like to know how (and if) this can be done in react/jsx.
JSX doesn't allow you to use dynamic HTML tags (dynamic components would work). That's because whenever you use something like <sometag ... />, an HTML element with tag name sometag is created. sometag is not resolved as a variable.
You also can't do what you have shown above. JSX expressions are not valid in place of a tag name.
Instead, you have to call React.createElement directly:
return React.createElement(
"h" + ele.title.importance,
{
key: elementNumber,
},
ele.title.content
);
Edit
My initial answer was not correct, you cannot use a variable directly and would need to use the createElement method described in Felix's answer. As noted below, and utilised in the blog post I originally linked, you can use object properties, so I've made an example of this, which hopefully will be useful as an answer to the question.
class Hello extends React.Component {
constructor() {
super();
this.state = {
tagName: "h1"
};
}
sizeChange(i) {
this.setState({
tagName: 'h' + i
});
}
changeButtons() {
var buttons = [];
for (let i=1; i<=6; i++) {
buttons.push(<button onClick={() => this.sizeChange(i)}>H{i}</button>);
}
return buttons;
}
render() {
return (
<div>
{this.changeButtons()}
<this.state.tagName>
Change Me
</this.state.tagName>
</div>
);
}
}
JSFiddle here
Original Answer
It can be done, although I don't think it is officially supported so may break in the future without warning. The caveat to this approach is that the variable name you choose for your tag cannot be the same as an HTML element.
var Demo = React.createClass({
render: function() {
const elementTag = 'h' + ele.title.importance;
return(
<elementTag>
Header x contents
</elementTag>
);
}
});
More explanation and a fuller example can be found here

Categories