14

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} &ndash; ${description}`}> ... </MyPanel>

Problem: In the rendered output, the &ndash; is being rendered literally as "&ndash;" instead of as "".


Attempted to construct some simple JSX with no quotes:

<MyPanel title={{name} &ndash; {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} &ndash; {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.
Jon Schneider
  • 25,758
  • 23
  • 142
  • 170
  • would `dangerouslySetInnerHTML` on the child elements of `` work? – Anthony Feb 15 '18 at 17:56
  • Assign it to a variable, then use it: `let temp_str = `${name} – ${description}`` – learner Feb 15 '18 at 17:56
  • @Tony: It's being used in a `title` attribute. – T.J. Crowder Feb 15 '18 at 17:57
  • @T.J.Crowder I saw the `title` prop but (perhaps incorrectly?) assumed `` would be rendering some actual element be it a h1, div, p etc. – Anthony Feb 15 '18 at 17:59
  • It would help people help you if you updated your question with a [mcve] demonstrating the problem, ideally a **runnable** one using Stack Snippets (the `[<>]` toolbar button). Stack Snippets support React, including JSX; [here's how to do one](http://meta.stackoverflow.com/questions/338537/). – T.J. Crowder Feb 15 '18 at 17:59
  • 1
    @Tony: You may be right, given the thing about a `span` having worked. Allowing HTML in a prop and then using `dangerouslySetInnerHTML`, though, would be...dangerous. :-D – T.J. Crowder Feb 15 '18 at 18:02
  • 1
    @T.J.Crowder oh most definitely! – Anthony Feb 15 '18 at 18:04
  • I'm AGAINS React replacing `–` with `–` in string variables because such feature brings a need to preprocess the string when you want to show it as is (e.g. to show a user comment). Such replacement is suitable only for JSX (which is almost a HTML). – Finesse Apr 22 '19 at 02:21

5 Answers5

17

You can avoid the superfluous span with a Fragment:

<MyPanel title={<>{name} &ndash; {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.

Luke Willis
  • 8,429
  • 4
  • 46
  • 79
17

Here are a few options (I outlined these in a more general answer awhile back):

  1. Easiest - Use Unicode

    <MyPanel title={ `${name} – ${description}` } />
    
  2. 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}`} />
    
  3. Last Resort - Insert raw HTML using dangerouslySetInnerHTML.

    title={`${name} &ndash; ${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} &ndash; ${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>
Brett DeWoody
  • 59,771
  • 29
  • 135
  • 184
3

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} &ndash; ${description}`) } />
samanime
  • 25,408
  • 15
  • 90
  • 139
  • The problem being, of course, that just like `dangerouslySetInnerHTML`, that means the component happily renders HTML content it receives via props which, like `dangerouslySetInnerHTML`, is...dangerous. :-) – T.J. Crowder Feb 15 '18 at 18:27
  • It is a bit better than `dangerouslySetInnerHTML` because it would then come back out as the `textarea` value, which would strip a lot of the HTML out. – samanime Feb 15 '18 at 18:34
1

Without knowing how <MyPanel /> works, I can only speculate that you could do something like the following:

<MyPanel title={`${name} &ndash; ${description}`}> ... </MyPanel>

MyPanel.js

render() {
    const { title } = this.props;

    return <div dangerouslySetInnerHTML={{ __html: title }} />;
}
Anthony
  • 6,422
  • 2
  • 17
  • 34
0

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&nbsp;1&#160;2&#xa0;3&nbsp;&mdash; 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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875