3

I'd like to do something like the following (but with str actually determined dynamically):

let str = "I like to drive my car fast";
str = str.replace("car", "<Text style={styles.red}>red car</Text>");

return (
  <Text>{str}</Text>
);

Is there anyway to insert JSX into other JSX using strings?

edit: and I'd like to avoid doing it via determining the index position of 'car' and then splitting it all up into parts and reconstructing by concatenating JSX fragments.

edit2: Just to be a bit more clear, what I want to do is dynamically read a string like "I like #pizza very much", identify the tag, and output the string with #pizza in blue (i.e. like a tag on twitter etc).

erv
  • 593
  • 8
  • 27

5 Answers5

1

no, it is not possible with React Native at the moment, yes many people suggested dangerouslySetInnerHTML but it won't work because it is not html under the hood. You can even look at props of the most common component View, it doesn't have anything closely similar to dangerouslySetInnerHTML

Evgeny Klimenchenko
  • 1,184
  • 1
  • 6
  • 15
1

If you want to change dynamically specify the field you want to change in a variable with can be changed later and split the text.

Snack: https://snack.expo.io/bdMxu7H_7

Check following example:

const text = 'I like to drive my car fast';
const replaceText = 'my car';
const replace = 'car';

export default function App() {
  const t = text.split(replace);
  return (
    <View style={styles.container}>
     <Text>{t[0]} <Text style={{color: 'red'}}>{replaceText}</Text> {t[1]}</Text>
    </View>
  );
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Ashwith Saldanha
  • 1,700
  • 1
  • 5
  • 15
  • I was hoping to do it without splitting the string up, but I see this might be the only way to do it. – erv Jul 01 '20 at 10:23
0

Give it a try, please:

let str = "I like to drive my car fast";

const arr = str.split(' ');
const reducer = (acc, cur, index) => {
    let previousVal = acc[acc.length - 1];
    if (
        previousVal &&
        previousVal.startsWith('car')
    ) {
        acc[acc.length - 1] = previousVal + ' ' + cur;
    } else {
        acc.push(cur);
    }
    return acc;
};

const text = arr.reduce(reducer, []);

return (
    <Text>
        {text.map((text) => {
            if (text.startsWith('car')) {
                return (
                    <Text style={styles.red}>
                        {text.replaceAll('car', 'red car')}{' '}
                    </Text>
                );
            }
            return `${text} `;
        })}
    </Text>
);
Shahnawaz Hossan
  • 2,695
  • 2
  • 13
  • 24
0

Why not 2 text elements?

<Text>I like to drive my <Text style={{ color: 'red' }}>{carVar}</Text></Text>
Maximillion Bartango
  • 1,533
  • 1
  • 19
  • 34
  • See my edit2. I think that might make it a bit clearer. – erv Jul 01 '20 at 10:20
  • if you want the color to be a variable, just change my example of 'red' to this colour variable you want. In your second edit you know that you want it blue, then just set it as blue. – Maximillion Bartango Jul 01 '20 at 10:35
  • You're misunderstanding. The whole string "I like to drive my (red) car" is dynamic. I can't hard code it as JSX. – erv Jul 01 '20 at 10:37
  • I see what you mean now my bad, I'll have a think for you, however using any sort of string to element evaluating is bad practice. – Maximillion Bartango Jul 01 '20 at 10:46
0

The way I ended up doing this is perhaps not the most efficient way, but it does work. I split the string up into its component words with str.split(" ") Then iterated through the created array to check whether a word started with #, @, or a link.

function compose(post) {
    let str = "";
    let blnFlag = false;
    let arrWords = [];
    let arrJSX = [];
    let array = post.split(/\s/);

    for (let word of array) {
        if (/^#.+/i.test(word)) { arrWords.push( {text: word, type: "tag"} ); } 
        else if (/^@.+/i.test(word)) { arrWords.push( {text: word, type: "mention"} ); }
        else if (/^(http|www).+/i.test(word)) { arrWords.push( {text: word, type: "link"} );} 
        else { arrWords.push( {text: word, type: "plain"} ); }
    }

    for (let word of arrWords) {
        if (word.type === "plain") {
            str += word.text + " ";
        } else {
            blnFlag = true;
            if(str) {
                arrJSX.push(<Text>{str}</Text>);
                str = "";
            }
            switch(word.type) {
                case "tag":
                    arrJSX.push(<Text style={styles.tags_mentions_links}>{word.text + " "}</Text>);
                    break;
                case "mention":
                    arrJSX.push(<Text style={styles.tags_mentions_links}>{word.text + " "}</Text>);
                    break;
                case "link":
                    arrJSX.push(<Text style={styles.tags_mentions_links} onPress={() => {Linking.openURL(word.text)}}>{word.text + " "}</Text>);
                    break;
            }
        }
    }
    if(!blnFlag) {
        arrJSX.push(<Text>{str}</Text>);
    }  

    return arrJSX;
}

Returning an array of JSX to the parent component allows you to map the array and output JSX directly

function Child(props) {
    let arrJSX = Util.compose(props.info.body);
    
    return (<P text=
              {arrJSX.map((jsx, index) => {
                  return(<Text key={index}>{jsx}</Text>)
              })}
            />)
}
erv
  • 593
  • 8
  • 27