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