Reputation: 7441
I have a FragmentedString
class, which is a string with one or multiple distinct substrings.
class FragmentedString {
str: string;
fragments: Array<{
id: string;
start: number;
end: number;
}>
}
All the verification needed to ensure this fragmented string is valid has been done previously. Let's say we have the following FragmentedString
, and every word "substring" is a substring.
This is a beautiful string with a few substrings, such as this substring and this other substring.
This is a beautiful string with a few [substring]s, such as this [substring] and this other [substring].
If I had to wrap every substring in an <a>
tag, I would insert them and set dangerouslySetInnerHtml
on a div. But what if I wanted to wrap these in React components? How can I do that? I thought of eval
, which not only sounds like a bad idea, but I'm not sure if it supports either TypeScript or JSX. What's your take?
To avoid the XY problem: I would like the state of a parent component to be changed depending on the substring clicked. It would be something like that in React.
class Component {
handleEvent(e) {
// use e.target.name to extract the substring id, and set the parent state using a method passed as a prop
}
render() {
return (
<p>String <a name="substring_id" onClick={this.handleEvent.bind(this)}>substring</a></p>
)
}
}
Note that even though I use <a>
here with its name
property for storing the id, it may not be the right decision, I let you judge.
Upvotes: 2
Views: 500
Reputation: 168913
Here's an example (might have off-by-one errors or other strangeness).
Live example here on CodeSandbox (can't be bothered to make it a snippet that'd work here because TypeScript, etc.).
import React from "react";
interface FragmentedString {
str: string;
fragments: Array<{
id: string;
start: number;
end: number;
}>;
}
function FragmentedStringRenderer({
fragmentedString,
renderFragment
}: {
fragmentedString: FragmentedString;
renderFragment: (id: string, content: string) => React.ReactChild;
}) {
const { str, fragments } = fragmentedString;
const out: React.ReactChild[] = [];
// Assumes `fragments` is in order and doesn't overlap
let currStart = 0;
for (let i = 0; i < fragments.length; i++) {
const { id, start, end } = fragments[i];
if (currStart < start) {
// need to add a plaintext fragment
out.push(<>{str.substring(currStart, start)}</>);
}
out.push(renderFragment(id, str.substring(start, end)));
currStart = end;
}
if (currStart < str.length) {
// need to add a final fragment
out.push(<>{str.substring(currStart)}</>);
}
return React.createElement(React.Fragment, {}, [...out]);
}
const fs: FragmentedString = {
str: "Hello, world! Are you happy?",
fragments: [
{ id: "noun", start: 7, end: 12 },
{ id: "adj", start: 22, end: 27 }
]
};
export default function App() {
return (
<div className="App">
<FragmentedStringRenderer
fragmentedString={fs}
renderFragment={(id: string, text: string) => (
<a href={`#${id}`}>{text}</a>
)}
/>
</div>
);
}
Upvotes: 1