deb
deb

Reputation: 7441

Wrap substrings in React components

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.

Upvotes: 2

Views: 500

Answers (1)

AKX
AKX

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

Related Questions