Skip to content Skip to sidebar Skip to footer

How To Highlight Matches Within A String With Jsx?

I have a custom autocomplete, so when you type, it will display a list of suggestions based on the input value. In the list, I would like to bold the characters that are the same

Solution 1:

Writing your own highlighting code could lead down a rabbit hole. In my answer, I assume only simple text (no HTML within the strings, no charset edge cases) and valid non-escaped RegExp pattern string.


Instead of building a new string, you could build a new array, in which you could put JSX.

A React component can also return an array of elements:

render() {
  // No need to wrap list items in an extra element!return [
    // Don't forget the keys :)<likey="A">First item</li>,
    <likey="B">Second item</li>,
    <likey="C">Third item</li>,
  ];
}

The logic behind

As a simple proof of concept, here's the logic we could use:

constdefaultHighlight = s => <em>{s}</em>;

// Needed if the target includes ambiguous characters that are valid regex operators.constescapeRegex = v => v.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");

/**
 * Case insensitive highlight which keeps the source casing.
 * @param {string} source text
 * @param {string} target to highlight within the source text
 * @param {Function} callback to define how to highlight the text
 * @returns {Array}
 */consthighlightWord = (source, target, callback) => {
  const res = [];

  if (!source) return res;
  if (!target) return source;
  
  const regex = newRegExp(escapeRegex(target), 'gi');

  let lastOffset = 0;
  
  // Uses replace callback, but not its return value
  source.replace(regex, (val, offset) => {
    // Push both the last part of the string, and the new part with the highlight
    res.push(
      source.substr(lastOffset, offset - lastOffset),
      // Replace the string with JSX or anything.
      (callback || defaultHighlight)(val)
    );
    lastOffset = offset + val.length;
  });
  
  // Push the last non-highlighted string
  res.push(source.substr(lastOffset));
  return res;
};

/**
 * React component that wraps our `highlightWord` util.
 */constHighlight = ({ source, target, children }) => 
  highlightWord(source, target, children);


constTEXT = 'This is a test.';

constExample = () => (
  <div><div>Nothing: "<Highlight />"</div><div>No target: "<Highlightsource={TEXT} />"</div><div>Default 'test': "<Highlightsource={TEXT}target="test" />"</div><div>Multiple custom with 't': 
      "<Highlightsource={TEXT}target="t">
        {s => <spanclassName="highlight">{s}</span>}
      </Highlight>"
    </div><div>Ambiguous target '.': 
      "<Highlightsource={TEXT}target=".">
        {s => <spanclassName="highlight">{s}</span>}
      </Highlight>"
    </div></div>
);


// Render itReactDOM.render(
  <Example />,
  document.getElementById("react")
);
.highlight {
  background-color: yellow;
}
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script><divid="react"></div>

No need to use dangerouslySetInnerHTML here.

This highlightWord function can take any function to wrap the matched string.

highlight(match, value) // default to `s => <em>{s}</em>`// orhighlight(match, value, s =><spanclassName="highlight">{s}</span>);

I'm doing minimal regex string escaping based on another answer on Stack Overflow.


The Highlight component

As shown, we can create a component so it's "more react"!

/**
 * React component that wraps our `highlightWord` util.
 */const Highlight = ({ source, target, children }) => 
  highlightWord(source, target, children);

Highlight.propTypes = {
  source: PropTypes.string,
  target: PropTypes.string,
  children: PropTypes.func,
};

Highlight.defaultProps = {
  source: null,
  target: null,
  children: null,
};

export default Highlight;

It uses a render prop, so you'd have to change your rendering to:

<ul>
  {matches.map((match, idx) => (
    <likey={idx}><Highlightsource={match}target={value}>
        {s => <strong>{s}</strong>}
      </Highlight></li>
  ))}
</ul>

Solution 2:

You just append your mapper as children inside your auto complete component.

<CustomAutocomplete>
  <ul>
    {
      matches.map(function(match, idx){
        let re = new RegExp(value, 'g');
        let str = match.replace(re, '<b>'+ value +'</b>');
        return (<li key={idx}>{str}</li>)
      })
    }
  </ul>
</CustomAutocomplete>

Post a Comment for "How To Highlight Matches Within A String With Jsx?"