2

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?

Edit

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.

11
  • Why not create a component that is capable of rendering a FragmentedString without any dangerouslySetInnerHtml? Commented Aug 4, 2021 at 10:36
  • Well, yes, the question is how. How do you insert React tags in a string? Commented Aug 4, 2021 at 10:45
  • You wouldn't do that in the first place. The component should instead split the string into several parts given the fragments and then create the correct markup from that parts wrapping the fragments in the desired tags. Commented Aug 4, 2021 at 10:47
  • 1
    Here's a recent answer of mine for a similar problem... stackoverflow.com/questions/68574191/… Commented Aug 4, 2021 at 11:02
  • 1
    @a2br Sure, see my answer :) Commented Aug 4, 2021 at 11:20

1 Answer 1

1

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>
  );
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.