Is it possible to dynamically construct JSX from javascript strings in React Native?

Viewed 167

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).

5 Answers

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

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>
  );
}

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>
);

Why not 2 text elements?

<Text>I like to drive my <Text style={{ color: 'red' }}>{carVar}</Text></Text>

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>)
              })}
            />)
}
Related