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

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

Related

Is there a way to manipulate rendered text in react component children?

I am trying to write a component that highlights text inside it's children recursively.
What I have been able to achieve, is to highlight the text only if it's explicitly provided in the body component, but I can't find a way to change the text of the component's render part.
Let's say I have the following HighlightText component:
(Note, that this is a concept component. The real component is much more complicated)
const HighlightText = ({highlight, children}) => {
const regex = new RegExp(`(${regexEscape(highlight)})`, 'gi');
return React.Children.map(children, child => {
// Found a text, can highlight
if (typeof child === 'string') {
const text = child.trim();
if (text) {
return text.split(regex).filter(p => p).map((p, i) =>
regex.test(p) ? <mark key={i}>{p}</mark> : <span>{p}</span>;
);
}
}
// If child is a react component, recurse through its children to find more text to highlight
if (React.isValidElement(child)) {
if (child.props && child.props.children) {
return HighlightText({children: child.props.children, highlight});
}
}
// Here I believe, should be another handling that handles the result of the render function to search for more text to highlight...
// For any other cases, leave the child as is.
return child;
})
}
And some component that renders something:
const SomeContent = () => <div>content</div>;
Now, I want to use the HighlightText component the following way:
ReactDOM.render(
<HighlightText highlight="e">
<SomeContent />
<p>some paragraph</p>
nude text
</HighlightText>
,document.body);
The resulted DOM of the the above code is:
<div>content</div>
<p><span>som</span><mark>e</mark><span> paragraph</span></p>
<span>nud</span><mark>e</mark><span> t</span><mark>e</mark><span>xt</span>
But I expect it to be:
<div><span>cont</span><mark>e</mark><span>nt</span></div>
<p><span>som</span><mark>e</mark><span> paragraph</span></p>
<span>nud</span><mark>e</mark><span> t</span><mark>e</mark><span>xt</span>
Any suggestions on how to handle the rendered part of the child component?
Eventually I managed to solve this problem using React.Context.
Not exactly as I expected, but I think it's even a better approach, because now I can decide what text to highlight.
It's similar to i18n and themes techniques in React. React.Context is best approach for these kind of text manipulations.

StencilJs/Jsx: render HTMLElements in nested component

This is my component:
#Component({
tag: "my-alert-list",
styleUrl: "alert-list.scss",
shadow: true,
})
export class AlertList {
#State() alertList: object[] = [];
#Method()
async appendAlert(
type: string,
message: string,
htmlContent: object,
canClose: boolean = false,
closeDelay: number
) {
let alertBoxElement = (
<my-alert-box
alert-type={type}
message={message}
can-close={canClose}
close-delay={closeDelay}
opened
>
{htmlContent}
</my-alert-box>
);
this.alertList = [
...this.alertList,
alertBoxElement
]
}
render() {
return (
<Host>
{this.alertList}
</Host>
);
}
}
The method appendAlert aims to append a new my-alert-box element to the list of alerts.
In same case i don't want to pass a simple text to the my-alert-box but some HTML block.
(my-alert-box has a receiver slot element and i verified that it works).
I tried to achieve this with the htmlContent variable as you can see, but of course it doesn't work if i do:
$('#alertlist')[0].appendAlert(type='info',message='', htmlContent=document.createElement('div'))
I receive the error:
[STENCIL-DEV-MODE] vNode passed as children has unexpected type.
Make sure it's using the correct h() function.
Empty objects can also be the cause, look for JSX comments that became objects.
Any idea on how can i achieve this?
It's not possible like this because JSX works differently. You could pass the htmlContent as a string and use innerHTML on my-alert-box but it's dangerous (XSS).
Ionic's ion-alert has the same limitation with the message prop... see https://ionicframework.com/docs/api/alert#properties which has a link to https://ionicframework.com/docs/techniques/security, and there they explain how they do some basic DOM sanitization (#ionic/core is also built with Stencil).

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

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.

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

Reactjs how to convert string to [object Object]

I have a menuItem component and its props is a Icon component created by me and called FontIcon.
And in menuItem props you can pass a name of the icon as a string, for example: leftIcon="face" but you can also pass a code for the component like this: leftIcon='<FontIcon style={{color:"red"}} className="face" />'.
In the first case, all works perfectly, the props is passed to the variable where is code for the component:
leftIcon = <FontIcon size={_props.fontSize} className={_props.leftIcon} />;
But this second way is not working. When the user passes all code I need to add something to this(this size value like above), at this point this adding works:
leftIcon = _props.leftIcon.replace(/\/>$/, " size={_props.fontSize}/>");
Here I've got if to check what has the user passed:
if (_props.leftIcon.match(/<.+\/>/)) {
leftIcon = _props.leftIcon.replace(/\/>$/, " size={_props.fontSize}/>");
} else {
leftIcon = <FontIcon size={_props.fontSize} className={_props.leftIcon} />;
}
But in this second way, I'm getting a string and it doesn't work. There is no icon, but there is a string with code:
So I've consoled log this and it's what I got:
The typeof this first is object but this second one is string.
So what can do to make this second way works?
If this code is in the render portion this should work;
if (_props.leftIcon.match(/<.+\/>/)) {
leftIcon = _props.leftIcon.replace(/\/>$/, " size={_props.fontSize}/>");
} else {
leftIcon = (<FontIcon size={_props.fontSize} className={_props.leftIcon} />);
}
Note the parenthesis.
There's a way to create component from string you can take a look on this answer
But I would suggest you to pass styles and / or class names and other parameters to your component rather then a string and then you could just have:
return <FontIcon class={condition ? 'firstClass' : 'secondClass'} ... />;

Categories