Replace subsring by an HTML tag in react - javascript

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

Related

Is it possible to change the color of a substring in a string or set it as different html tag if it is a prop set as string?

Having a React component that accepts as a prop a string:
interface MyProps {
myInput: string;
}
export function MyComponent({ myInput }: MyProps) {
...
return (
<div>
{myInput}
</div>
);
};
And this component is used somewhere else:
<MyComponent myInput="please contact us at test#test.com" />
My question is, can we change the color of the email address in this case? For example to be blue.
Or even better, to wrap that text into:
<a href="mailto:test#test.com" target="_blank">
test#test.com
</a>
Not sure if it's possible to do something like this if the prop is of type string.
Sure you can do it based on the string you provide but it'd be much easier to provide the email address as a separate property to MyComponent.
Without changing the component I'd use some simple RegExp to get the email from the string and then you'd be able to do anything you want with it.
A quick example with an incomplete and simplified RegExp:
const SUPER_SIMPLE_EMAIL_REGEX = /[a-z0-9.]{1,}#test.com/gi; // Don't use this one
export function MyComponent({ myInput }: MyProps) {
const match = myInput.match(SUPER_SIMPLE_EMAIL_REGEX);
if (!match.length) {
return (
<div>
{myInput}
</div>
);
}
const textWithoutEmail = myInput.replace(match[0], '');
return (
<div>
<span>{textWithoutEmail}</span>
<a href={`mailto:${match[0]}`} target="_blank">
match[0]
</a>
</div>
);
};
This is a very simplified solution but I think you can use this in your case.

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.

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

Why does function-returned data not render in react?

Admittedly learning react & jsx, but can't for the life of me figure out why this does not render:
function generateItem(item){
return `<li><a href=${item.url}>${item.text}</a></li>`;
}
function MyList ({items}){
return (
<div className="list">
<ul>
{items.forEach(item => {
generateItem(item);
})}
</ul>
</div>
);
};
All it outputs (when included in my app) is an empty list:
<div class="list"><ul></ul></div>
I'm sure it's something basic and fundamental. But there I am.
https://codesandbox.io/s/relaxed-stallman-81ety
items.forEach does not return anything... the things inside curly brackets in jsx will end up transpiled to an argument in a call to React.createElement, so they need to be an expression and return a value. (check here the transpiled version)
Try items.map(item => generateItem(item)) or simply items.map(generateItem) instead.
here's your code edited (I also changed generateItem to return jsx instead of a string)
https://codesandbox.io/s/frosty-sutherland-fp7kh

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.

Categories