Reputation: 485
I have a description stored as html I want to render in my component. However, before it can be rendered I need to replace parts of the description with JSX components. However, unlike other questions I've seen that ask this I need to replace more than one type of thing in the description with JSX components. This requires multiple regex statements. Take the following description as an example:
<div style="white-space: pre-line;">
This is my video.
0:00 Intro
4:12 Point 1
9:12 Point 2
14:12 Closing Point
Check out my website at https://example.com
#tag #tag2 #tag3
</div>
In this description all links need to be wrapped in an link element, timestamps need to be converted into a link that changes the video time and hashtags need to be converted into a link that takes the user to the search page.
This is how I formatted the description when I was using jQuery:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="description" style="white-space: pre-line;"></div>
<script>
var description = `
This is my video.
0:00 Intro
4:12 Point 1
9:12 Point 2
14:12 Closing Point
Check out my website at https://example.com
#tag #tag2 #tag3
`;
$('#description').html(createLinks(createHashtagLinks(formatTimestamps(description))));
function createLinks(text) {
return text.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>')
}
function createHashtagLinks(text) {
return text.replace(/#\w*[a-zA-Z]\w*/g, '<a href="/videos?search=$&">$&</a>');
}
function formatTimestamps(text) {
return text.replace(/^[0-5]?\d(?::(?:[0-5]?\d)){1,2}/gm, function (match) {
var timeArray = match.split(':').reverse();
var seconds = 0;
var i = 1;
for (let unit of timeArray) {
seconds += unit * i;
i *= 60;
}
return `<a data-seconds="${seconds}" href="#">${match}</a>`
});
}
</script>
<a>
elements I need to instead replace them with <Link>
components. How can I do this in React?
Upvotes: 0
Views: 2937
Reputation: 5926
Consider the following approach:
&<>'"
characters to their respective XML entities.For example:
const customParse = (rawStr) => {
const str = rawStr.replace(/[&<>'"]/g, (m) => `&#${m.codePointAt(0)};`);
const wrapped = str
.replace(/https?:\/\/\S+/g, (m) => `<Link>${m}</Link>`)
.replace(/#\w*[a-zA-Z]\w*/g, (m) => `<Tag>${m}</Tag>`);
const dom = new DOMParser().parseFromString(
`<root>${wrapped}</root>`,
"application/xml"
);
return [...dom.documentElement.childNodes];
};
const RenderedOutput = ({ text }) => (
<pre>
{customParse(text).map((node, idx) => {
if (node.nodeType === Node.TEXT_NODE) {
return <React.Fragment key={idx}>{node.data}</React.Fragment>;
} else {
switch (node.nodeName) {
case "Link":
return <Link key={idx} url={node.textContent} />;
case "Tag":
return <Tag key={idx} tag={node.textContent} />;
default:
throw new Error("not implemented");
}
}
})}
</pre>
);
You could implement additional custom logic as needed if you also want to create and read from attribute lists and so on.
Upvotes: 1