How to highlight matches within a string with JSX?

后端 未结 2 2011
情歌与酒
情歌与酒 2021-01-14 21:06

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 th

2条回答
  •  自闭症患者
    2021-01-14 21:29

    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. React can render an array of nodes directly.

    The logic behind

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

    const defaultHighlight = s => {s};
    
    // Needed if the target includes ambiguous characters that are valid regex operators.
    const escapeRegex = 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}
     */
    const highlightWord = (source, target, callback) => {
      const res = [];
    
      if (!source) return res;
      if (!target) return source;
      
      const regex = new RegExp(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.
     */
    const Highlight = ({ source, target, children }) => 
      highlightWord(source, target, children);
    
    
    const TEXT = 'This is a test.';
    
    const Example = () => (
      
    Nothing: ""
    No target: ""
    Default 'test': ""
    Multiple custom with 't': " {s => {s}} "
    Ambiguous target '.': " {s => {s}} "
    ); // Render it ReactDOM.render( , document.getElementById("react") );
    .highlight {
      background-color: yellow;
    }
    
    
    

    No need to use dangerouslySetInnerHTML here.

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

    highlight(match, value) // default to `s => {s}`
    // or
    highlight(match, value, s => {s});
    

    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:

      {matches.map((match, idx) => (
    • {s => {s}}
    • ))}

提交回复
热议问题