I am working on React and I have created a form using TextField from @material-ui/core. The user will enter the text, it will pass to a server in the backend, and a new text will appear. I can easily do this by changing the value of the TextField. My problem is, when I show the output in the TextField, I want some of the strings to be highlighted as shown in the figures. Before After
I can get the indices and the tokens from the backend, but I didn't find a way to highlight parts of the TextField value. All what I found is styling the whole value using InputProps. I have tried using this answer, and react-highlight-words package, but I can't set the value using those as it will return an object and the shown value will be [object object] rather than a partially highlighted string I also tried find-substring-in-text-and-change-style-programmatically and uses TextField rather than Text, but I want only one TextField, not a different TextField for each word. I have added what I did based on this answer in my code.
Here is a simplified version of my code
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();`
app.js
import React, { Component } from 'react';
import './App.css';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import HomePage from './HomePage';
import 'bootstrap/dist/css/bootstrap.css';
class App extends Component {
render() {
return (
<div className="App">
<>
<Router>
<Switch>
<Route path='/' exact component={HomePage} />
</Switch>
</Router>
</>
</div>
);
}
}
export default App;
HomePage.js
import React from 'react'
import { TextField, Button } from "@material-ui/core";
const HomePage = () => {
const [value, setValue] = React.useState();
const [indices, setIndices] = React.useState();
const [substrings, setSubstrings] = React.useState();
const handleChange = (event) => {
setValue(event.target.value)
}
const handleChange2 = (event) => {
// What to set?
}
const handleClear = () => {
setValue("")
}
const handleSubmit = () => {
let axios = require('axios')
let data = {
"text": value
}
axios.post('http://127.0.0.1:8000/predict', data)
.then(response => (setValue(response.data.value),
setIndices(response.data.indices),
setSubstrings(response.data.substrings)
))
}
return (
<form noValidate autoComplete="off">
{/*This is the block that I tried from this link https://stackoverflow.com/questions/57808195/react-native-find-substring-in-text-and-change-style-programmatically*/}
{/*
{['red','black','green'].map(item => (
<TextField
InputProps={{
style: {
background: item,
},
}}
value={item}
onChange={handleChange2}
/>
))}
*/}
<TextField
value={value}
rowsMax={15}
InputProps={{
style: {
fontWeight: 500,
},
}}
variant="outlined"
onChange={handleChange}
/>
<Button onClick={handleClear}>Clear</Button>
<Button onClick={handleSubmit}>Submit</Button>
</form>
);
};
export default HomePage;
Is there a way to do this? or a simpler approach like passing an HTML code to the value?
UPDATE: I solved the problem using native contentEditable native
as Julien Ripet suggests. Here is the updataed HomePage.js code.
import React from 'react'
import {Button } from "@material-ui/core";
const HomePage = () => {
// This handler will listen to the paste event. It will serves as "paste as plain text" to avoid pasting the style.
const handlePaste = (event) => {
// cancel paste
event.preventDefault();
// get text representation of clipboard
var text = (event.originalEvent || event).clipboardData.getData('text/plain');
// insert text manually
document.execCommand("insertText", false, text);
}
// Set the textbox to empty when the clear button is clicked
const handleClear = () => {
var inputText = document.getElementById("textbox");
inputText.innerHTML = "";
}
// Send the text to the server, pass the returned value with its indices to the highlight function to be highlighted and rendered on the same box
const handleSubmit = () => {
// Get the object of the textbox
var inputText = document.getElementById("textbox");
// use innerHTML instead of innerText if you want a string with the html tags
var onlyText = inputText.innerText.trim().replaceAll("\n\n","\n")
// Send data to the server, the response will be (value, indices). Pass it to the highlight function to be highlighted and rendered to the textbox.
/*
let axios = require('axios')
let data = {
"text": onlyText
}
axios.post('http://127.0.0.1:8000/predict', data)
.then(response => (highlight(response.data.value, response.data.indices)))
*/
// Example
var text = "We are trying this text just to see the output";
var indices = [{"start":1, "end":3},{"start":7, "end":8}];
highlight(text, indices)
}
const highlight = (text, indices) => {
// Get the object of the textbox
var inputText = document.getElementById("textbox");
// Split the string to an array of strings for easier processing
text = text.split(" ");
// enclose to be highlighted words with an HTML span of class highlight
for (var i = 0; i < indices.length; i++) {
text[indices[i].start] = "<span class='highlight'>" + text[indices[i].start]
text[indices[i].end -1] = text[indices[i].end -1] + "</span>"
}
// Join the array of strings into one string
var new_text = text.join(' ')
// Update the content of the textbox object with the highlighted text
inputText.innerHTML = new_text;
}
return (
<form noValidate autoComplete="off">
<p
className="textbox"
variant="outlined"
id="textbox"
onPaste={handlePaste} /** Handle the paset event to avoid pasting the style*/
fontWeight = {500}
contentEditable = {true}
></p>
<Button onClick={handleClear}>Clear</Button>
<Button onClick={handleSubmit}>Submit</Button>
</form>
);
};
export default HomePage;
Added to App.css
.highlight {
background-color: yellow;
}
.textbox {
border: 1px solid #000000;
}
Here are some stackoverflow questions that helped me:
get the text content from a contenteditable div through javascript
Javascript trick for "paste as plain text" in execCommand
` as mentioned in the first answer. Would using native `
` will be more secure? if not, how can I pass an `html` tag to the value?
– Amal Apr 03 '21 at 08:14