How to highlight words with react.js

Hy!

Today I needed to create a small auto-complete form like google with react.js. You write the word in an text-input and you get a list of items. Now the tricky thing was highlighting the world you were searching by in the title of each item.

The result is like this

An issue with this approach is that if you have a filter with multiple words then it will only highlight the first word. Can be fixed but a lot more code would be needed.

Under the hood

Under the hood the view gets 2 inputs:

  • The name (props.data.name);
  • The filter word (props.activeFilter): needed in order to know which words to highlight;

Sometimes you might have an activeFilter like this “cat fer” which contains 2 words. In order to handle this we will only highlight the first word:

// activeFilter comes from the props
var activeFilter  = this.props.activeFilter.split(' ')[0];

Because we need to handle case insensitive (notice the i in gi) matches we need a regex instead of a string:

var regex = new RegExp('' + activeFilter + '', 'gi' );

Now we need to split the name and get the replacements. We need to use the .match because we want to keep the same case after the text has been replaced. So if original text is “hacKhat” and you want to highlight the word “kh”, you still want your output to be “hacKhat” instead of “hackhat”.

var name          = this.props.data.name;
var segments      = name.split(regex);
var replacements  = name.match(regex);

Now it comes the core part where we join the segments together:

// Here we store each child
var titleChildren = [];
segments.forEach(function(segment, i){
    // Here we add the first segment
    titleChildren.push(React.DOM.span({className: css.getClass('segment')}, segment));
    // If last segment then don't add the highlight part
    if(segments.length === i + 1){return;}
    // Here we add the highlighted part
    titleChildren.push(React.DOM.span({
        className: css.getClasses({
            segment   : true,
            highlight : true
        })
    }, replacements[i]));
}.bind(this))

Interpreting the code step by step

So what the script does exactly?

Let’s get define “HackHAT” as name and “a b” as filter.

  • The filter “a b” becomes “a” (because we don’t handle multi word filters);
  • Then we create a case insensitive regex with the word “a”; This will match the “a” and “A” in “HackHAT”;
  • The name is then divided by the regex so the segments variable will be equal to ["H", "ckH", "T"];
  • The name is matched against the regex and the replacements variable will be equal to ["a", "A"];
  • Now we join the 2 arrays like this (let’s say segments is s and replacements is r): var titleChildren = [s[0], bold(r[0]), s[1], bold(r[1]), s[2]]; This is an oversimplification because you don’t just call bold(r[2]) but you make a span element which has a class that makes it bold; Also this is a loop;

Further reading

You might wonder what is css.getClasses, but that is out of the context of this post. If you want to learn more about CSS in big applications take a look at smart-css.