2

I am returning a set of information from Spotify in a React Component and want to interrogate the JSON that is returned and highlight the original search term within the artist name. so for example, if you search 'bus' and one of the artists returned is Kate Bush, then this would be highlighted green in 'Kate BUSh'. At the moment I am calling a function from within render(). However, what I get rendered is:

Kate <span style="color:green">Bus</span>h

How do I get render() to read the HTML as HTML (so that Bus would just be green) rather than rendering as text? Relevant code from the React Component below:

// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
    let searchTerm = this.props.searchTerm;

    if (displayString.indexOf(searchTerm) !== -1) {
        displayString = displayString.replace(searchTerm, '<span style="color:green">'+searchTerm+'</span>');
    }
    return displayString;
}

render() {
    return (
        <div className="Track" id="Track">
            <div className="Track-information">
                <h3>{this.underlineSearch(this.props.trackName)}</h3>
                <p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>
            </div>
        </div>
    );
}
1
  • does style={{color: 'green'}} work? Commented Jan 16, 2018 at 21:24

3 Answers 3

0

Your underlineSearch function needs to return React Elements, but right now it is returning a string. You could use a Fragment to make it work:

// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
    const searchTerm = this.props.searchTerm;
    const indexOfSearchTerm = displayString.indexOf(searchTerm);

    let node;
    if (indexOfSearchTerm === -1) {
        node = displayString;
    } else {
        node = (
          <React.Fragment>
            {displayString.substr(0, indexOfSearchTerm)}
            <span style={{color: 'green'}}>
              {displayString.substr(indexOfSearchTerm, searchTerm.length)}
            </span>
            {displayString.substr(indexOfSearchTerm + searchTerm.length)}
          </React.Fragment>
        );
    }

    return node;
}
Sign up to request clarification or add additional context in comments.

Comments

0

To make your solution even more reusable you can make underlineSearch and wrapper with your styles for highlighting into 2 separate components. Even more, you can search for multiple occurrences of your searchTerm with regex. Found a similar SO question here. I slightly adapted one of the answers there according to your needs, but all credit goes to this amazing and neat solution for highlighting matches of a string in longer texts. Here is the code:

const Match = ({ children }) => (
  <span style={{'color':'green'}}>{children}</span>
);

const HighlightMatches = ({ text, searchTerm }) => {
  let keyCount = 0;

  let splits = text.split(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
  let matches = text.match(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
  let result = [];

  for (let i = 0; i < splits.length; ++i) {
    result.push(splits[i]);
    if (i < splits.length - 1) {
      result.push(<Match key={++keyCount}>{matches[i]}</Match>);
    }
  }

  return (
    <p>{result}</p>
  );
};

Then in your main component where you render everything you can do this:

render() {
   <div className="Track" id="Track">
      <div className="Track-information">
        <h3>
          <HighlightMatches text={this.props.trackName} searchTerm={this.props.searchTerm}/>
        </h3>
        <p>
          <HighlightMatches text={this.props.artistName} searchTerm={this.props.searchTerm} /> |
          <HighlightMatches text={this.props.albumName} searchTerm={this.props.searchTerm} />
      </div>
    </div>
}

To me this seems like the most react-like approach to solve the problem :)

Comments

0

While you can use dangerouslySetInnerHTML (), as the name suggests it is extremely dangerous, since it is prone to XSS attacks, for example:

{artist: "Kate Bush<script> giveMeAllYourCookies()</script>"}

You can split the displayString into an array and render it.

Please note that that my implementation of underlineSearch is buggy, and will not work if there are more than one match.

class Main extends React.Component {
  underlineSearch(displayString) {
    let searchTerm = this.props.searchTerm;
    var index = 0;
    var results = [];
    var offset = 0;
    while(true) {
      const index = displayString.indexOf(searchTerm, offset);
      if(index < 0) {
        results.push(<span>{displayString.substr(offset)}</span>);
        break;
      }
      results.push(<span> {displayString.substr(offset, index)}</span>);
      results.push(<strong style={{color: 'green'}}> {displayString.substr(index, searchTerm.length)}</strong>);
      offset = index + searchTerm.length;
    }
    return results;
  }
  
  render() {
    return <div>
                <h3>{this.underlineSearch(this.props.trackName)}</h3>
                <p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>

    </div>
  }

}

ReactDOM.render(<Main
  trackName="Magic Buses"
  artistName="Kate Bush"
  albumName="Kate Bush Alubm"
  searchTerm="Bus"
/>, document.getElementById('main'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id='main'></div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.