7

I have a React.js app with a search bar which is similar to, i.e., Google. A user can type some text into an input field, and matching data is fetched and displayed in a dropdown menu below the input.

My issue: I want to bold certain words that are shown in my dropdown - specifically the words in the string that the user has not typed into the input field themselves.

So, for instance, if a user types in the search bar "blue", below it will display all data in the DB which contains the word "blue" i.e. "blue shoes" or "blue sky". I want it that the word that is not typed into the searchbar to be bolded --> sky or shoes

My makeBold function does not work because a) it displays on the browser as <b>blue</b> sky rather than actually bolded; b) it would bold the keyword the user typed in rather than the other words)

** keyword is passed to this component by the parent, it contains the text the user has written in the input field

** searchResults contains the list of fetched search results passed as an array of objects

const SearchResults = ({ keyword, searchResults }) => {
  
   const showDropdown = () => {
    return searchResults.map((item) => {
      if (item.term.includes(keyword.toLowerCase())) {
        return (
          <div key={item.term}>
            <ul>
              <li className="input-fields search-results">
                {makeBold(item.searchterm)} ({item.nrResults})
              </li>
            </ul>
          </div>
        );
      }
    });
  };

  const makeBold = (input) => {
      var re = new RegExp(keyword, 'g')
      return (
          input.replace(re, '<b>'+keyword+ '</b>')
      )
  }

  return <div>{keyword ? showDropdown () : null}</div>;
};

So my question is how to bold specific text in my string which has not typed into the input field? Thanks!

critical_maas
  • 127
  • 1
  • 3
  • 9
  • What if you bold everything by default and un-bold the matched search term? – MonkeyZeus Jul 22 '20 at 14:55
  • A possibility but the line `item.replace(re, ''+keyword+ '')` does not actually work. It displays on the browser as `blue sky` – critical_maas Jul 22 '20 at 14:59
  • It sounds like whatever code is consuming `makeBold` assumes the data cannot be trusted and is converting special chars to entities for literal output in the browser. Instead of a returning a string you could try returning a JDOM object instead or fix the consumer of `makeBold`. Regardless, it is impossible for me to guess how to fix your code without seeing more of it. – MonkeyZeus Jul 22 '20 at 15:02

4 Answers4

5

You need to dangerouslySetInnerHTML

Sample code below. This is what it does.

  1. Make all text bold via CSS
  2. Our keyword is wrapped in a i tag. This is rendered with normal font weight via a CSS rule.

Please note that below is a code sample to be used in react context/OP application, this will not work in this site.

const makeBold = (item, keyword) => {
      var re = new RegExp(keyword, 'g')
      return (
          item.replace(re, '<i>'+keyword+ '</i>')
      )
  }
  
  console.log(makeBold('blue shoes', 'blue'));
.text-bold {
 font-weight: bold;
}

.text-bold i {
 font-weight: normal;
}
<div className="text-bold">
   <ul>
      <li dangerouslySetInnerHTML={{__html: makeBold(item, 'blue')}} />
   </ul>
</div>

Instead of dangouresly setting the html you can also use the package react-html-parser. This avoids the use of dangerouslySetInnerHTML

Then the code would look like

import ReactHtmlParser from 'react-html-parser';

<div className="text-bold">
       <ul>
          <li> {ReactHtmlParser(makeBold(item, 'blue')) }
       </ul>
    </div>
kiranvj
  • 32,342
  • 7
  • 71
  • 76
  • @MonkeyZeus Its dangerous thats the reason its named like that. Please check the link XSS link under https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml Part 2: We can either do it as per the link you provided or use a package which does that for you (DRY approach) – kiranvj Jul 22 '20 at 15:25
  • @MonkeyZeus `copy+paste?` can u show a website where you see the above code other than react functions!. `So providing insecure code using a rebranded innerHTML` dangerouslySetInnerHTML is there for a reason(and its not exactly equal to innerHTML), otherwise React wont even have it. If you are talking about the npm package, thats not the rebrand of innerHTML, please check the package source in github before commenting. If you have a different approach(other than the 2 mentioned in above answer) please provide that. – kiranvj Jul 22 '20 at 16:11
2

You could use the dangerouslySetInnerHTML attribute.

But as its name suggests, it can be dangerous. If this input is coming from an external source then you have to make sure that it does not contain any unwanted scripts or links!

return (
  <div dangerouslySetInnerHTML={{
    __html: item.replace(re, '<b>' + keyword + '</b>')
  }} />
)
cy3er
  • 1,699
  • 11
  • 14
  • @MonkeyZeus It's named dangerous because they wanted to make it clear that it's the developer's job to make sure the inserted html is safe. The answer you linked is a good way to make it safe. – cy3er Jul 22 '20 at 15:32
2

For the sake of completeness, here are my comments in an answer form.

Your problem is basically how to convert a String to JSX. Have a look at How do I convert a string to jsx? - you need to use dangerouslySetInnerHTML. Be aware that the solution there is vulnerable to XSS attacks though so make sure you trust your input.

An example of how to use dangerouslySetInnerHTML is below:

<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

<script type="text/babel">
  const keyword = "beans";

  const makeBold = item => {
    const re = new RegExp(keyword, "g");
    return item.replace(re, "<b>" + keyword + "</b>");
  };
  
  const items = [
    "I really like beans",
    "I really don't like beans",
    "I really like eggs"
  ]

  ReactDOM.render(<div>
    <ul>
      {
        items.map((item, i) => {
          return (
            <li
              dangerouslySetInnerHTML={{ __html: makeBold(item) }}
              key={i}
            />
          )
        })
      }
    </ul>
  </div>, document.getElementById('root') );

</script>

<div id="root"></div>
James Whiteley
  • 3,363
  • 1
  • 19
  • 46
0

this work for /\d{1,2}(.\d)*%/, it could work for u

const MainMessage = ({ msg }) => {
  const newExpresion = [];
  const newMsg = msg.split(/\s/);
  
  newExpresion.push("");
  let i = 0;
  newMsg.forEach((element) => {
    if (element.match(/\d{1,2}(.\d)*%/)) {
      newExpresion.push(<b> {element} </b>);
      newExpresion.push("");
      i += 2;
    } else {
      newExpresion[i] = `${newExpresion[i]} ${element}`;
    }
  });
  return <div>{newExpresion.map((item) => item)}</div>;
};