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 as the input value.
So if I have a list of suggestions: "alligator", "lima", "lime", and I typed "li", then the suggestions would look like this:
- alligator
- lima
- lime
I have this simple map
in my jsx file:
<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>
where value
is the input value. It displays the list but in this string format
- al<b>li</b>gator
- <b>li</b>ma
- <b>li</b>me
Not sure how to go about with React. I thought of using dangerouslyinnerhtml
or something like that, but I think that's a last resort thing. I would like to avoid that if possible.
This my autocomplete component:
class Autocomplete extends Component{
constructor(props){
super(props);
this.state = {
value: '',
matches: [],
showMatches: false
}
}
searchListing(){
api.call {
that.setState({
showMatches: true,
matches: a
});
})
}
}
handleOnChangeInput(e){
let value = e.target.value;
this.setState({ value: value})
if(value !== ''){
this.searchListing(e);
}else{
// console.log("value", e.target.value);
this.setState({
showMatches: false,
matches: []
})
}
}
render(){
let matches = this.state.matches;
let value = this.state.value;
let matchesHtml;
if(this.state.showMatches){
matchesHtml = <ul>
{matches.map(function(match, idx){
let re = new RegExp(value, 'g');
let str = match.replace(re, '<b>'+ value +'</b>');
return <li key={idx} dangerouslySetInnerHTML={{__html: str}}></li>
})}
</ul>
}
return(
<div>
<input placeholder="type a name" onChange={this.handleOnChangeInput}/>
{matchesHtml}
</div>
);
}
}
To highlight text using React, we can create our own component where we check whether each word matches what we want to highlight. to create the Highlighted component to highlight the text set as the highlight option.
What is JSX? JSX stands for JavaScript XML. JSX allows us to write HTML in React. JSX makes it easier to write and add HTML in React.
Highlight Text Using the Mark Tag Method in JavaScriptIf you surround any text inside the mark tag, the browser will automatically highlight it in a striking yellow color. This will make highlighting a searched text quite a simple task then.
You can embed any JavaScript expression in JSX by wrapping it in curly braces. For example, 2 + 2 , user. firstName , and formatName(user) are all valid expressions.
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 :) <li key="A">First item</li>, <li key="B">Second item</li>, <li key="C">Third item</li>, ]; }
As a simple proof of concept, here's the logic we could use:
const defaultHighlight = s => <em>{s}</em>;
// 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 = () => (
<div>
<div>Nothing: "<Highlight />"</div>
<div>No target: "<Highlight source={TEXT} />"</div>
<div>Default 'test': "<Highlight source={TEXT} target="test" />"</div>
<div>Multiple custom with 't':
"<Highlight source={TEXT} target="t">
{s => <span className="highlight">{s}</span>}
</Highlight>"
</div>
<div>Ambiguous target '.':
"<Highlight source={TEXT} target=".">
{s => <span className="highlight">{s}</span>}
</Highlight>"
</div>
</div>
);
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
.highlight {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="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>`
// or
highlight(match, value, s => <span className="highlight">{s}</span>);
I'm doing minimal regex string escaping based on another answer on Stack Overflow.
Highlight
componentAs 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) => (
<li key={idx}>
<Highlight source={match} target={value}>
{s => <strong>{s}</strong>}
</Highlight>
</li>
))}
</ul>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With