Highlighting a specific word in a text document using React JS - javascript

const boldText = () => {
const text=document.getElementById('blog-text');
const originalText = text.innerHTML;
const input = document.getElementById('text-to-search');
if (input.value) {
const word=input.value.trim();
const regexp = new RegExp(word,'gi');
reactStringReplace(originalText,regexp, (match, i) => (
<span key={i} style={{ fontWeight: 'bold' }}>{match}</span>
));
}
}
I tried to use this code in order to highlight a specific word given by the user in a document(input by user) and replace the word in the original document but I am not receiving any change on clicking the button in the document.
I also tried this code below but still was awarded with no changes in the document.
*Below that I have also provided the js snippet
const boldText = () => {
let btext = document.getElementById('blog-text');
console.log(document.getElementById('blog-text').value);
let input = document.getElementById('text-to-search').value;
console.log(document.getElementById('text-to-search').value);
input = input.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
let pattern = new RegExp(`${input}`, "gi");
reactStringReplace(btext,pattern, (match, i) => (
<span key={i} style={{ fontWeight: 'bold' }}>{match}</span>
));
}
This is the Complete JavaScript file
import React, { useState } from 'react'
import "./Blog.css"
import reactStringReplace from 'react-string-replace';
export default function Blog() {
// const boldText = () => {
// const text=document.getElementById('blog-text');
// const originalText=text.innerHTML
// const input=document.getElementById('text-to-search');
// if(input.value){
// const word=input.value.trim();
// const regexp=new RegExp(word,'gi');
// reactStringReplace(originalText,regexp, (match, i) => (
// <span key={i} style={{ fontWeight: 'bold' }}>{match}</span>
// ));
// }
const boldText = () => {
let btext = document.getElementById('blog-text');
console.log(document.getElementById('blog-text').value);
let input = document.getElementById('text-to-search').value;
console.log(document.getElementById('text-to-search').value);
input = input.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
let pattern = new RegExp(`${input}`, "gi");
reactStringReplace(btext,pattern, (match, i) => (
<span key={i} style={{ fontWeight: 'bold' }}>{match}</span>
));
}
const handleOnChangetit = (event) => {
setTitText(event.target.value);
}
const handleOnChangedes = (event) => {
setDesText(event.target.value);
}
const [titleText, setTitText] = useState("Enter Title here....");
const [desText, setDesText] = useState("Enter Text here....");
return (
<>
<div className="blogButtons">
<input className="searchbar" type="search" id="text-to-search"
placeholder="Search" aria-label="Search..." />
<button className="btn btn-outline-success" onClick={boldText} type="submit"><i
className="fa-solid fa-magnifying-glass"> Bold Text</i></button>
</div>
<div className="textbox">
<textarea type="text" value={titleText} onChange={handleOnChangetit}
className='blogtitle' />
<textarea type="text" id="blog-text" value={desText} onChange={handleOnChangedes}
className='blogarea' />
</div>
</>
)
}

It is a bit of an anti-pattern to access the DOM directly in your react code. Because of re-rendering and rebuilding the actual DOM on screen. It is better to use hooks to build references to your JSX elements into your components for when you need to access or mutate the values.
I can't see the inputs or the blog text components, so I'll have to give you an out of context example:
import { useState, useRef, useEffect } from "React"
import reactStringReplace from 'react-string-replace'
const MyComponent = ({ blogText }) => {
const [inputValue, setInputValue] = useState('')
const blogText = useRef()
useEffect(() => {
const searchValue = inputValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
let pattern = new RegExp(`${searchValue}`, "gi")
reactStringReplace(blogText.current, pattern, (match, i) => (
<span key={i} style={{ fontWeight: 'bold' }}>{match}</span>
))
}, [inputValue, blogText])
return (
<div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
type="text"
className="text-to-search"
/>
<p className="blog-text" ref={blogText}>
{blogText}
</p>
</div>
)
}
Explanation of Hooks
useState
This is a hook that returns an array containing both a value and a function that sets the value
These values are destructured from the array at use of the hook
Assign the value to your input, and an onChange handler that updates the values when the user types
useEffect
Calls a function whenever a render is triggered
The render can be targeted to a specific change, in this case, the change of the input
Put the targeted change in the Dependency Array, the second argument to useEffect
useRef
Use this function to assign a value to a DOM element that will persist between renders.
This value is "safe" from rerenders, and is safe to use inside a dependency array of useEffect for example.
Use this instead of DOM methods
Access the referenced value with blogText.current
Essentially, I am using useEffect to listen for changes to inputValue, which is using State to control the text input. When this state value changes, it runs a function that calls reactStringReplace with the reference to blogText.

Related

React - toggle text and class in an HTML element?

I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox

React Parsing string as html and applying a function to DOM with forwardRef

On a ReactJs project I try to parse with com.wiris.js.JsPluginViewer.parseElement(ref, true, function () {}); function.
According to the docs, that function is to be applied to specific elements on the DOM. In the samples with pure javascript, they first set innerHTML of dom element and then apply the function.
So I thought that it would be handled using refs in React. (not sure if its the best way)
I have an array which includes some string to be rendered as html. I took all refs as an array with useRef([]) and then set each element's ref using refs.current[index]
According to the type I directly render string with dangerouslySetInnerHTML or use a wrapper component to render with a child component on which I would use that special function.
But I couldn't reach innerHTML property of the ref before applying the function in the WirisWrapper. I tried ref.innerHTML and ref.current.innerHTML
Parser as Parent
import { useRef, createRef } from 'react';
import WirisWrapper from './WirisWrapper';
const Parser = ({ itemsArray }) => {
let refs = useRef([]);
refs.current = itemsArray.map((ref, index) => (refs.current[index] = createRef()));
return (
<div>
{itemsArray.map((item, index) =>
item.type === 'math' ? (
<WirisWrapper
ref={el => (refs.current[index] = el)}
key={index}
mString={item.html}
/>
) : (
<div key={index} dangerouslySetInnerHTML={{ __html: item.html}}>
</div>
)
)}
</div>
);
};
export default Parser;
WirisWrapper as Child
import { forwardRef, useEffect } from 'react';
const WirisWrapper = forwardRef((props, ref) => {
const { mString } = props;
useEffect(() => {
if (ref.current && com.wiris.js.JsPluginViewer) {
ref.current.innerHTML = mString;
com.wiris.js.JsPluginViewer.parseElement(ref, true, function () {});
}
}, [ref, com.wiris.js.JsPluginViewer]);
return <div ref={ref}></div>;
});
export default WirisWrapper;
refs.current = itemsArray.map((ref, index) => (refs.current[index] = createRef())); looks to be creating a new React ref each render cycle and mutating the existing array at the same time.
You want to only create React refs if they don't previously exist. Map the itemsArray array to a new array each render, returning existing refs or creating new refs.
refs.current = itemsArray.map((ref, i) => refs.current[index] ?? createRef()));
Then just access the ref by index when mapping the UI.
{itemsArray.map((item, index) =>
item.type === 'math' ? (
<WirisWrapper
ref={refs.current[index]}
key={index}
mString={item.html}
/>
) : (
<div key={index} dangerouslySetInnerHTML={{ __html: item.html}}>
</div>
)
)}

how to set focus to next input on enter key press in react js with refs

Iam using multiple inputs inside maps i want to set focus to next input when i click enter in react Hooks.
With the help of refs
Iam using material ui text field for getting input
I tried in react class component wihtout ref it works with error but in hooks it not works
class compomnent code:
constructor(props) {
this.state = {}
}
inputRefs = [];
_handleKeyPress = e => {
const {currentTarget} = e;
let inputindex = this.inputRefs.indexOf(currentTarget)
if (inputindex < this.inputRefs.length - 1) {
this.inputRefs[inputindex + 1].focus()
}
else {
this.inputRefs[0].focus()
}
};
Inside render in added this within map function
this.state.data.map((data) => return (
<TextField
inputProps = {{onKeyPress:(e) => this.function1(e, data)}}
onChange={this.changevaluefunction}
inputRef={ref => this.inputRefs.push(ref)}
onFocus={this.handleFocus} ref={`input${id}`} /> ))
I have implemented the solution in a different way with the functional component. I have taken the 4 fields and seated its ref with the createRef hook.
I can see from your solution, you wanted to move focus to the next input element whenever you press Enter key on the current element.
I am passing the next target element argument in the onKeyUp handler along with the actual event and then detecting whether the Enter key is pressed or not. If Enter key is pressed and the targetElem is present then I am moving focus to the passed targetElem. By this way you have better control over the inputs.
You can see my solution here
https://codesandbox.io/s/friendly-leftpad-2nx91?file=/src/App.js
import React, { useRef } from "react";
import TextField from "#material-ui/core/TextField";
import "./styles.css";
const inputs = [
{
id: "fName",
label: "First Name"
},
{
id: "lName",
label: "Last Name"
},
{
id: "gender",
label: "Gender"
},
{
id: "address",
label: "Address"
}
];
export default function App() {
const myRefs = useRef([]);
const handleKeyUp = (e, targetElem) => {
if (e.key === "Enter" && targetElem) {
targetElem.focus();
}
};
return (
<div>
{inputs.map((ipt, i) => (
<TextField
onKeyUp={(e) =>
handleKeyUp(e, myRefs.current[i === inputs.length - 1 ? 0 : i + 1])
}
inputRef={(el) => (myRefs.current[i] = el)}
id={ipt.id}
fullWidth
style={{ marginBottom: 20 }}
label={ipt.label}
variant="outlined"
key={ipt.id}
/>
))}
</div>
);
}
You can convert this.inputRefs into a React ref so it persists through renders, and other than this you pretty much remove all references to any this object.
Example Component:
const LENGTH = 10;
const clamp = (min, max, val) => Math.max(min, Math.min(val, max));
export default function App() {
const [data] = useState([...Array(LENGTH).keys()]);
const inputRefs = useRef([]); // <-- ref to hold input refs
const handleKeyPress = index => () => { // <-- enclose in scope
const nextIndex = clamp(0, data.length - 1, index + 1); // <-- get next index
inputRefs.current[nextIndex].focus(); // <-- get ref and focus
};
return (
<div className="App">
{data.map((data, index) => (
<div key={index}>
<TextField
inputProps={{ onKeyPress: handleKeyPress(index) }} // <-- pass index
inputRef={(ref) => (inputRefs.current[index] = ref)} // <-- save input ref
/>
</div>
))}
</div>
);
}
If you are mapping the input field and want to focus on click, you can directly give the id attribute to the input and pass the array id.
After that, you can pass id inside a function as a parameter, and get it by document.getElementById(id).focus().

Get text Input Value React

I'm trying to track the text input value using states but "e.target.value" doesn't seem to work(maybe because my component is declared as a function). Is there any other way I can do it?
const UncontrolledDiagram = ({ sentence }) => {
// create diagrams schema
const [schema, { onChange, addNode, connect, removeNode }] = useSchema(initialSchema);
const [selected, setSelected] = useState([]);
const [textInput,setInput]=useState('')
const handleTextChange=(e)=>{
setInput(e.target.value);
console.log(textInput);
}
This is the input field I am tracking:
const conditionalDisplay=(id)=>{
const nodeToCheck=schema.nodes.find(node=>node.id=== id);
if(nodeToCheck.form===null){
return(
<div>
<label>Form: </label><input style={{ width: '25px', height: '12px' }} onChange={handleTextChange} type='text'></input>
<button className='buttonInputSubmit'>+</button>
</div>
)
}
else{
return(
<div style={{display: 'flex',margin: '0'}}>
<label>Form: </label><p style={{color: 'yellow', marginLeft:'2px'}}>{nodeToCheck.form}</p>
</div>
)
}
}
It works, your console.log(textInput) still has the old state because state is set asynchronously. It will set the state as soon as your function has been fully executed.
const handleTextChange=(e)=>{
setInput(e.target.value);
}
useEffect(() => {
console.log(textInput);
}, [textInput])

React Controlled Form with Child /Parent component

I'm building a controlled form with dynamic fields.
The Parent component get data from a redux store and then set state with the values.
I don't want to make it with too much code lines so I turn the dynamic fields into a component.
States stay in the parent component and I use props to pass the handlechange function.
Parent :
function EditAbout(props) {
const [img, setImg] = useState("");
const [body, setBody] = useState(props.about.body);
const [instagram, setInstagram] = useState(props.about.links.instagram);
const [linkedin, setLinkedIn] = useState(props.about.links.linkedin);
const [press, setPress] = useState(props.about.press)
const handleSubmit = (e) => {
// Submit the change to redux
};
// set states with redux store
useEffect(() => {
setBody(props.about.body);
setInstagram(props.about.links.instagram);
setLinkedIn(props.about.links.linkedin);
setPress(props.about.press);
}, []);
const handleChangeChild = (e, index) => {
e.preventDefault();
let articles = press
const {value, name } = e.target
if (name === "title") {
articles[index].title = value;
} else {
articles[index].link = value;
}
setPress(articles)
console.log(articles[index])
}
return (
<Box>
<h1>CHANGE ABOUT ME</h1>
<Input
label="Image"
name="img"
type="file"
variant="outlined"
margin="normal"
onChange={(e) => setImg(e.target.files)}
/>
<Input
label="body"
value={body}
name="body"
onChange={(e) => setBody(e.target.value)}
variant="outlined"
multiline
rowsMax={12}
margin="normal"
/>
<Input
label="instagram"
value={instagram}
name="instagram"
variant="outlined"
margin="normal"
onChange={(e) => setInstagram(e.target.value)}
/>
<Input
label="Linkedin"
value={linkedin}
name="linkedin"
variant="outlined"
margin="normal"
onChange={(e) => setLinkedIn(e.target.value)}
/>
<Child press={press} onChange={handleChangeChild} />
{props.loading ? (
<CircularProgress color="black" />
) : (
<Button onClick={handleSubmit} variant="contained">
Send
</Button>
)}
</Box>
);
}
Child :
function Child(props) {
const { press, onChange } = props;
const inputsMarkup = () =>
press.map((article, index) => (
<div key={`press${index}`} style={{ display: "flex" }}>
<input
name="title"
value={press[index].title}
onChange={(e) => onChange(e, index)}
/>
<input
name="link"
value={press[index].link}
onChange={(e) => onChange(e, index)}
/>
<button>Delete</button>
</div>
));
return (
<div>
<h1>Press :</h1>
{inputsMarkup()}
</div>
);
}
Everything is fine when I'm typing in the Parent inputs. But when I'm using Child fields state update for one character but come back at its previous state right after.
It also doesn't display the character change. I can only see it in the console.
Thanks you in advance for your help
The problem is that you're mutating the state directly. When you create the articles variable (let articles = press) you don't actually create a copy and articles doesn't actually contain the value. It's only a reference to that value, which points to the object’s location in memory.
So when you update articles[index].title in your handleChangeChild function, you're actually changing the press state too. You might think that's fine, but without calling setPress() React will not be aware of the change. So, although the state value is changed, you won't see it because React won't re-render it.
You need to create a copy of the press array using .map() and create a copy of the updated array element. You can find the updated handleChangeChild() below:
const handleChangeChild = (e, index) => {
e.preventDefault();
const { value, name } = e.target;
setPress(
// .map() returns a new array
press.map((item, i) => {
// if the current item is not the one we need to update, just return it
if (i !== index) {
return item;
}
// create a new object by copying the item
const updatedItem = {
...item,
};
// we can safely update the properties now it won't affect the state
if (name === 'title') {
updatedItem.title = value;
} else {
updatedItem.link = value;
}
return updatedItem;
}),
);
};

Categories