Search code examples
javascriptreactjsregexreplace

ReactJS - Replacing first occurrence of string pattern with custom component


I have a number of strings with this format:

'The model of that car is [assignment: any car model] and it has [assignment: any number] horsepower'

I want to display this string in my UI and replace every occurrence of [custom: text] with a textbox. My CustomComponent implements a textbox with a placeholder. I want the placeholder to be the text that comes between '[custom:' and the closing squared bracket.

I'm using reactStringReplace in order to replace string patterns with react components. The main problem is that reactStringReplace will replace every matching occurrence at the same time. Even if I match only the first occurrence, this will result in multiple checkboxes with the same placeholder.

I even tried to match only one occurrence removing '/g' at the end of the regex but still all occurrences are replaced

This is my code:

const reactStringReplace = require('react-string-replace');

const description = 'The model of that car is [custom: any car model] and it has [custom: any number] horsepower'

function formatString(input){
    const regex =  /\[assignment: (.*?)\]/g;
    const regex_assignment =  /\[assignment: (.*?)\]/;
    let formattedString = input;
    let match;
    let placeholder;
    let newString = input;

    while ((match = regex.exec(input)) !== null) {
        placeholder = match[1];

        newString = reactStringReplace(newString,regex_assignment, ()=> (<CustomComponent placeholder={placeholder}/>))
    }

    return(newString);
}

return (
    <div class = 'description'>
        <div>{formatString(description)}</div>
    </div>
    );

Solution

  • The return value of reactStringReplace is an array, not a string (and it has to be, to do its job). But you're treating it as a string.

    You don't need to have the loop in your code, that's one of the things reactStringReplace does for you, as they shown in the "More realistic example" section. So it's much simpler (I changed the regular expression to use custom rather than assignment since that's what's in the description string):

    function formatString(input) {
        return reactStringReplace(input, /\[custom: (.*?)\]/g, (match, i) => (
            <CustomComponent key={i} placeholder={match} />
        ));
    }
    

    (That's using the index for key, which is usually an anti-pattern [see the Pitfall in this section of the React documentation], but I think choosing the right key is outside the scope of the question.)

    Live Example:

    const reactStringReplace = module.exports;
    
    function formatString(input) {
        return reactStringReplace(input, /\[custom: (.*?)\]/g, (match, i) => (
            <CustomComponent key={i} placeholder={match} />
        ));
    }
    
    // Stand-in for CustomComponent
    function CustomComponent({ placeholder }) {
        return <span className="placeholder">{placeholder}</span>;
    }
    
    function Example() {
        const description =
            "The model of that car is [custom: any car model] and it has [custom: any number] horsepower";
        return (
            <div className="description">
                <div>{formatString(description)}</div>
            </div>
        );
    }
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<Example />);
    .placeholder {
        color: blue;
        font-weight: bold;
    }
    <div id="root"></div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
    <script>
    const module = {};
    </script>
    <script src="https://unpkg.com/[email protected]/index.js"></script>