CaptainStiggz
CaptainStiggz

Reputation: 1897

reactStringReplace: trouble formatting bold/italic/underline text with capture group regex

Shared question from: https://github.com/iansinnott/react-string-replace/issues/33

react-string-replace: https://github.com/iansinnott/react-string-replace

I'm having trouble using reactStringReplace to format text with bold/italic tags. I have it set up as follows:

var text1 = "[b]Testing bold [i]and italic[/i] tags[/b]"
var text2 = "[b]Testing bold [i]and italic tags[/b][/i]"

let replaced = reactStringReplace(text1, /\[b\]([\s\S]+)\[\/b\]/g, (match, i) => {
   return <b key={i}>{match}</b>
})
replaced = reactStringReplace(replaced, /\[i\]([\s\S]+)\[\/i\]/g, (match, i) => {
   return <i key={i}>{match}</i>
})

// result (html)
<b>Testing [i]bold and italic[/i] tags</b>
<b>Testing [i]bold and italic tags</b>[/i]

I'm not sure if this is a problem with reactStringReplace, with the regex I'm using, or something else. If I apply the italic replace first, I get italic tags where I'd expect them to be, but the [b] tags remain unchanged. Is this use case possible using reactStringReplace or do I need to use dangerouslySetInnerHtml?

Bonus: is reactStringReplace capable of handling unbalanced tags, or improperly formatted tags as in text2 or should I be doing some pre-processing to ensure the strings are properly balanced and formatted?

Upvotes: 2

Views: 1496

Answers (1)

wp78de
wp78de

Reputation: 18950

Your sample has multiple issues,

  • you do not use the recommended JSX key format <i key={match + i}>{match}</i> (see below)
  • your matches overlap
  • your greedy + quantifier may match too much.
  • missing ;s, etc.

Ad. 1: Quoting from the full example:

In many React examples you will see the i or index variable used
as the key for JSX tags (such as the <a> tags in this example), however in this case we are iterating in three separate loops. This means that we cannot use key={i} because all three JSX tags could get the same key.

Ad. 2: The most crucial part here, however, is your matches are overlapping, and this does not go well with reactStringReplace.

A demonstration:

const text = 'Hey @ian_sinn#reactconf';
let replacedText;

// Match @-mentions
replacedText = reactStringReplace(text, /@(\w+)/g, (match, i) => (
  <b key={match + i}>@{match}</b>
));

// Match hashtags
replacedText = reactStringReplace(replacedText, /#(\w+)/g, (match, i) => (
  <a key={match + i} href={`https://twitter.com/hashtag/${match}`}>#{match}</a>
));

We have distinct matches in this sample: first, @ian_sinn is matched and replaced, then #reactconf, thus the replacement works as intended; however, if you change the first regex replacing \w with a . you get an overlapping match: Now, the second replacement will not be applied.

It works if you avoid overlapping matches:

render() {    
    const text = '[b]Testing bold [i]and italic[/i] tags[/b]';
    let replacedText;

    replacedText = reactStringReplace(text, /\[b\]((?:(?!\[\/b\]|\[i\]|\[\/i\]).)*)/g, (match, i) => (
      <b key={match + i}>{match}</b> 
    ));

    replacedText = reactStringReplace(replacedText, /\[i\]((?:(?!\[\/b\]|\[i\]|\[\/i\]).)*)/g, (match, i) => (
      <b><i key={match + i}>{match}</i></b>
    ));

    replacedText = reactStringReplace(replacedText, /\[\/i\]((?:(?!\[\/b\]|\[i\]|\[\/i\]).)*)/g, (match, i) => (
      <b key={match + i}>{match}</b>
    ));

    replacedText = reactStringReplace(replacedText, /\[\/b\]((?:(?!\[\/b\]|\[i\]|\[\/i\]|$).)*)$/g, (match, i) => (
      <b key={match + i}>{match}</b>
    ));

    return (
      <div>
        {replacedText}
      </div>
    );
  },
});

But this requires you to throw more sophisticated regex at the problem, gives you ugly markup, and arguably, defeats the purpose of the whole adventure...

Ad. 3: Compare this with a simple vanilla regex replace using an ungreedy match: [\s\S]+?

const str = `[b]Testing bold [i]and italic[/i] tags[/b]`;
const result = str.replace(/\[b\]([\s\S]+?)\[\/b\]/gm, `<b>$1</b>`)
                  .replace(/\[i\]([\s\S]+?)\[\/i\]/gm, `<i>$1</i>`);
console.log('Substitution result: ', result);


Verdict: Sad, but now official. As of now, reactStringReplace does not handle nested replacements:

since replacements can change the data type to something other than strings. If you look here you'll see that it only runs replacements on strings. Once your first replacement has been run the element in the result array will be an object (i.e. <b>...</b>) so no replacements will be run on the inner string.

Upvotes: 1

Related Questions