Invisible reacaptcha position problem when submitting redux form - javascript

I have problem with invisible recaptcha in react.
I have registration form in modal window and if the recaptcha is
showed and i am detected as a bot, the puzzle box is sending the
user to the bottom of the page. The recaptcha puzzle is adding a
div to the DOM and for positioning is using the top: max of the
screen property.
This is the npm package that i am using
https://www.npmjs.com/package/react-google-recaptcha
export class Registration extends React.Component {
onRecaptchaChange = (recaptchaResponse) => {
const data = {
form: this.state.form,
recaptchaResponse,
};
this.props.submitQuickSignupDetails(data);
};
submitQuickSignupDetails = (form) => {
this.setState({ form: form.toJS() });
this.captcha.reset();
this.captcha.execute();
};
render() {
return (
<React.Fragment>
<RegistrationForm
onSubmit={this.submitQuickSignupDetails}
onAlreadyRegistered={this.props.hideSignupModal}
/>
<ReCaptcha
ref={(ref) => { this.captcha = ref; }}
sitekey={XXXXXXX}
size="invisible"
badge="bottomleft"
onChange={this.onRecaptchaChange}
/>
</React.Fragment>
);
}
}
For now the only possible solution that is just resolving my problem only the first time that the recaptcha is triggered is:
submitQuickSignupDetails = (form) => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
const nodesList = mutation.addedNodes;
for (let idx = 0; idx < nodesList.length; idx += 1) {
const node = nodesList.item(idx);
if (node.tagName && node.tagName === 'DIV' && node.querySelector('iframe[title="recaptcha challenge"]')) {
const visibilityInterval = setInterval(() => {
if (node.style.visibility === 'visible') {
node.style.top = `${window.scrollY + 150}px`;
clearInterval(visibilityInterval);
observer.disconnect();
}
}, 250);
}
}
});
});
this.setState({ form: form.toJS() });
this.captcha.reset();
this.captcha.execute().then(() => {
observer.observe(document.body, {
childList: true,
});
});
};
But if the user make mistake during resolving the recaptcha puzzle the recaptcha is sending the user to the bottom of the page again

Related

Highlight words within a ReactMarkdown component as they are spoken using SpeechSynthesis API

I am attempting to highlight words as they are spoken using SpeechSynthesis API in my React component. The text being read is in markdown format. This markdown is being rendered on the page using the "react-markdown" package. It might also be important to note that I am using a package "speak-tts" to interact with the SpeechSynthesis API.
To do this, I have wrapped the ReactMarkdown component in a div with id "article-content" so I can access all the child nodes. I loop over the child nodes until I find the child node which contains the index for the end of the current word being spoken. Then I am appending a span tag with my desired styles from the beginning of the child's textContent until the end position of the current word being spoken.
The problem with this, is that the child element loses any HTML tags within it (e.g. ).
Below is the code I have so far:
Component:
import ReactMarkdown from "react-markdown";
import Speech from "speak-tts";
export const Article: FunctionComponent<{
markdown: string;
}> = ({ markdown }) => {
const { play, pause, isPlaying } = useSpeech(article.post?.content.markdown);
return (
<div>
<button onClick={isPlaying ? play : pause}>
Play
</button>
<div id="article-content">
<ReactMarkdown>
{markdown}
</ReactMarkdown>
</div>
);
}
useSpeech hook:
const useSpeech = (text?: string) => {
const [isPlaying, setIsPlaying] = useState(false);
const [speech, setSpeech] = useState<Speech>();
const router = useRouter();
useEffect(() => {
const speech = new Speech();
speech.init({
rate: 3,
pitch: 1.4,
});
setSpeech(speech);
router.events.on("routeChangeStart", () => speech.cancel());
window.addEventListener("beforeunload", () => speech.cancel());
// Cleanup function
return () => {
router.events.off("routeChangeStart", () => speech.cancel());
window.removeEventListener("beforeunload", () => speech.cancel());
};
}, []);
const removeHighlightedText = () => {
const highlightedText = document.querySelector('span[style*="underline"]');
if (highlightedText) {
highlightedText.style.textDecoration = "none";
}
};
let totalChars = 0;
const play = () => {
if (speech.paused()) {
speech.resume();
setIsPlaying(true);
return;
}
if (!("speechSynthesis" in window)) {
alert("Your browser does not support text-to-speech.");
return;
}
if (speech && text) {
let charIndex = 0;
speech.speak({
// Replace all periods with commas so that the charIndex is not reset at the end of the sentence.
text: text
.replaceAll(".", ",")
.replace(/\n/g, " "),
listeners: {
onstart: () => {
removeHighlightedText();
setIsPlaying(true);
},
onended: () => {
removeHighlightedText();
setIsPlaying(false);
},
onboundary: (event: SpeechSynthesisEvent) => {
charIndex = event.charIndex;
const children =
document.getElementById("article-content")?.childNodes;
if (!children) return;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child && child.textContent) {
// Get the total characters played so far
totalChars += child.textContent.length;
if (totalChars >= charIndex) {
// Highlight the current word
const start =
charIndex - (totalChars - child.textContent.length);
// After the first child, the highlight index is off by 1, not sure why
const end = start + event.charLength - (i > 0 ? 1 : 0);
child.innerHTML =
"<span style='text-decoration: underline; text-underline-position: under; text-decoration-color: #fb923c'>" +
child.textContent.slice(0, end) +
"</span>" +
child.textContent.slice(end);
break;
}
}
}
totalChars = 0;
},
},
});
}
};
const pause = () => {
setIsPlaying(false);
speech.pause();
};
return {
play,
pause,
isPlaying,
};
};

Trying to delete a key and values from an object, but when I try to delete it breaks some functionality

I am trying to create an add div button and a delete div button. When you select a certain div and click delete, I want to delete only that key from the object. The issue is when I delete and then try to create a new div, it doesn't create the new divs anymore...Not sure what i'm doing wrong or why it only kind of works.
import "./styles.css";
import {
useEffect,
useState
} from "react";
// The parent component
const App = () => {
const [textBoxDivs, setTextBoxDivs] = useState({});
const addNewTextBox = () => {
const numOfTextBoxDivs = Object.keys(textBoxDivs).length;
console.log(numOfTextBoxDivs, "num");
setTextBoxDivs({
...textBoxDivs,
[`div${numOfTextBoxDivs + 1}`]: {
isSelected: false,
innerText: "text"
}
});
};
const selectItem = (e) => {
const nextState = { ...textBoxDivs
};
Object.keys(nextState).forEach((k) => {
nextState[k].isSelected = false;
});
nextState[e.target.id].isSelected = true;
setTextBoxDivs(nextState);
};
const removeSelectedItem = () => {
const nextState = { ...textBoxDivs
};
if (Object.keys(textBoxDivs).length > 0) {
Object.keys(textBoxDivs).map((key) => {
if (textBoxDivs[key].isSelected) {
delete nextState[key];
return setTextBoxDivs(nextState);
}
return null;
});
}
};
return ( <
div >
<
button onClick = {
() => addNewTextBox()
} >
Click me to create a selectable div <
/button> <
button onClick = {
(e) => removeSelectedItem(e)
} >
Click me to delete a selectable div <
/button> {
Object.keys(textBoxDivs).length > 0 &&
Object.keys(textBoxDivs).map((key, index) => {
return ( <
div style = {
{
border: textBoxDivs[key].isSelected ?
"2px solid green" :
"unset"
}
}
onClick = {
(e) => selectItem(e)
}
key = {
index
}
id = {
key
} >
{
textBoxDivs[key].innerText
} <
/div>
);
})
} <
/div>
);
};
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The problem in your code in the function addNewTextBox, specifically in the line
[`div${numOfTextBoxDivs + 1}`]: {
Because it does not necessarily mean that your are adding a new line. In this case, you are assigning a value to (div + number), but sometimes that already exists. For example, of you change that line for a truly unique number, such as date, it works:
const addNewTextBox = () => {
const numOfTextBoxDivs = Object.keys(textBoxDivs).length;
console.log(numOfTextBoxDivs, "num");
setTextBoxDivs({
...textBoxDivs,
[`div${new Date().getTime()}`]: {
isSelected: false,
innerText: "text"
}
});
};
Update selectItem() then it works:
const selectItem = (e) => {
const nextState = { ...textBoxDivs, setTextBoxDivs }; // setTextBoxDivs was missing
Object.keys(nextState).forEach((k) => {
nextState[k].isSelected = false;
});
nextState[e.target.id].isSelected = true;
setTextBoxDivs(nextState);
};

How can the render be synced with the state on pagination click with react?

I created a suggestions search and its built to break up the fetch based on the current page. The state is console.loged correctly, but the render is one page click event behind. This is obviously not the behavior we want. It seems like the state is being updated fine. I have tried to refactor the code difference ways, and even tried this.forceUpdate()
Here is the code
SearchOrderBar.js
import React, { Component } from "react";
import {Input, Label, Table, Icon, Header, Menu} from 'semantic-ui-react';
import "./SearchOrderBar.css";
// import { resolve } from "dns";
// import PropTypes from 'prop-types';
import Pagination from '../Search/Pagination';
class SearchOrderBar extends Component {
constructor(props) {
super(props);
this.text = "";
this.state = {
suggestions: [],
addToQuery: false,
Query: [],
pagesNeeded: 0,
page: 1
};
let searchTerm = null;
const {pageLimit = null, keyTimer = null, } = props;
this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 10;
this.handlePageClick = this.handlePageClick.bind(this);
this.fetchCallBack = this.fetchCallBack.bind(this);
// this.addToQuery = this.addToQuery.bind(this);
this.keyUpHandler = this.keyUpHandler.bind(this);
this.keyDownHandler = this.keyDownHandler.bind(this);
}
handlePageClick(page){
this.forceUpdate();
this.setState({
page: page
})
this.fetchCallBack();
}
//This fetch should be called in a dynamic switch case
fetchCallBack() {
let y = this.pageLimit;
let x = this.state.page > 1 ? (this.pageLimit*this.state.page) - this.pageLimit : 0;
// Return a promise
return new Promise((resolve, reject) => {
let searchTerm = this.searchTerm;
return fetch(`http://localhost:5000/api/searchorders/${searchTerm}/${x}/${y}`)
.then(res => {
if (!res.ok) {
throw res;
}
// Convert serialized response into json
return res.json()
}).then(data => {
//Use data
let searchTerm = data.map(data => {
let rData = {};
rData = data;
return rData;
})
this.item = searchTerm;
//console.log('here from callback')
this.setState({
suggestions: []
})
return searchTerm;
}).then( data => {
// console.log(this.totalRecords)sd
//console.log(data)
if (searchTerm.length === 0) {
this.setState({
suggestions: [],
rangeCount_URL: `http://localhost:5000/api/searchorderscount/${searchTerm}`
});
} else {
const suggestions = data.filter(function(v){
if(Object.values(v).includes(searchTerm.toLowerCase()) !== -1 || Object.values(v).includes(searchTerm.toUpperCase()) !== -1){
return v
}
})
console.log(suggestions)
this.text = searchTerm;
this.setState({ suggestions: suggestions.sort()});
}
})
})
}
pageCountCallBack(){
return new Promise((resolve, reject) => {
let searchTerm = this.searchTerm;
return fetch(`http://localhost:5000/api/searchorderscount/${searchTerm}/`)
.then(res => {
if (!res.ok) {
throw res;
}
// Convert serialized response into json
return res.json()
}).then(data => {
//Use data
let searchTerm = data.map(data => {
let rData = {};
rData = data;
return rData;
})
this.item = searchTerm;
// console.log('here from Page Count callback')
this.renderSuggestions();
resolve(searchTerm)
})
})
}
keyUpHandler = (e) => {
if(e.target.value.length >= 3){
this.keyTimer = setTimeout(this.countFetch(e), 1500);
} else {
this.setState(() => {
return {
suggestions : [],
pagesNeeded : 0
}
})
clearTimeout(this.keyTimer);
}
}
keyDownHandler = (e) => {
clearTimeout(this.keyTimer);
}
//Any time text is changed in the text field
countFetch = (e) => {
const value = e.target.value;
this.searchTerm = value;
this.pageCountCallBack().then(data => {
const totalRecords = data[0].rows;
this.setState(() => {
return {pagesNeeded : Math.ceil(totalRecords / this.pageLimit)}
})
//console.log("total" + totalRecords);
//console.log("page limit"+this.pageLimit);
//console.log("Needed" + this.state.pagesNeeded );
})
this.fetchCallBack();
}
renderSuggestions() {
//const { suggestions } = this.state;
const tableStyle = {
'tableLayout': 'fixed',
'overflowWrap': 'break-word'
}
return (
<Table style={tableStyle} celled>
{this.state.suggestions.length === 0 ?
(<Table.Body>
<Table.Cell colSpan="7">
<div className="ui fluid warning icon message">
<Icon name="exclamation triangle" size="huge" color="orange"/>
<div className="content">
<Header>No Records Found</Header>
<p>Try Seaching by one of the following:</p>
<ul>
<dt>Name</dt>
<dt>Order Number</dt>
<dt>Address (Shipping or Billing )</dt>
<dt>Phone Number</dt>
<dt>Email</dt>
</ul>
</div>
</div>
</Table.Cell>
</Table.Body>)
: (
<>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Order#</Table.HeaderCell>
<Table.HeaderCell>Billing Address</Table.HeaderCell>
<Table.HeaderCell>Shipping Address</Table.HeaderCell>
<Table.HeaderCell>Email</Table.HeaderCell>
<Table.HeaderCell>Phone Number</Table.HeaderCell>
<Table.HeaderCell>Sales Channel</Table.HeaderCell>
<Table.HeaderCell>Order Date</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{this.state.suggestions.map((item, index) => (
<Table.Row className="hoverRow">
<Table.Cell key={index} onClick={() => this.addToQuery(item)}>
{item.customerPO}
</Table.Cell>
<Table.Cell>
{item.billToAddress}
</Table.Cell>
<Table.Cell>{item.shipToAddress}</Table.Cell>
<Table.Cell>{item.email}</Table.Cell>
<Table.Cell>{item.phone}</Table.Cell>
<Table.Cell>{item.customerContact}</Table.Cell>
<Table.Cell>{item.dateCreated}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</>
)
}
<Pagination key={this.state.pagesNeeded} tableCols="7" pagesNeeded={this.state.pagesNeeded} btnLimit={5} pageClick={this.handlePageClick} currPage={this.state.page} pageLimit={this.pageLimit}/>
</Table>
);
}
handleIconClick(){
console.log('icon clicked ' + this.state.Query )
}
render() {
const {text} = this.state
//console.log(this.state)
return (
<>
<div className="App-Component">
<div className="App-Search">
<Input icon={{ name: 'search', circular: true, link: true, onClick: () => this.handleIconClick() }} placeholder="Search" value={text} type="text" onKeyUp={this.keyUpHandler} onKeyDown={this.keyDownHandler} className="App-Search"/>
{this.renderSuggestions()}
</div>
</div>
</>
);
}
}
export default SearchOrderBar;
Here is the pagination but I don't think this matters as much for the solution. It is relevant for the page button click.
import React, {Component} from 'react';
import {Input, Label, Table, Icon, Header, Menu} from 'semantic-ui-react'
/**
* Helper Method for creating a range of Numbers
* Range )( )
*/
const range = (from, to, step = 1) => {
let i = from;
const range = [];
while (i<=to) {
range.push(i);
i+=step;
}
}
export default class Pagination extends Component {
constructor(props){
super(props)
const { totalRecords = null, pageNeighbours = 0, rangeCount_URL = this.props.rangeCount_URL, pageArray = [] } = props;
this.pageArray = typeof pageArray === 'array' ? pageArray : [];
}
renderPagination = () => {
//console.log("hello from pagination");
let n = this.props.pagesNeeded;
let pArray = [];
let page = this.props.currPage;
//console.log(n)
if (page > 1){
pArray.push(<Menu.Item as='a' icon onClick={() => this.props.pageClick(page-1)}>
<Icon name='chevron left' />
</Menu.Item>)
}
for(let i = (page >1 ? page-1: page); pArray.length < (page > this.props.btnLimit ? this.props.btnLimit+1 : this.props.btnLimit); i++){
//console.log(i);
pArray.push(<Menu.Item index={i} className={i == page ? 'active' : ''} onClick={() => this.props.pageClick(i)} as='a'>{i}</Menu.Item>)
}
if (page < n){
pArray.push(<Menu.Item as='a' icon onClick={() => this.props.pageClick(page+1)}>
<Icon name='chevron right' />
</Menu.Item>)
}
this.pageArray = pArray;
return pArray;
}
render(){
const pageCount = (() => {
const totalRecords = this.totalRecords;
if(totalRecords > 0){
return (this.totalPages = Math.ceil(this.totalRecords / this.props.pageLimit))
}
})();
//console.log(this.pageArray);
return(
<Table.Footer>
{ this.props.pagesNeeded > 1 &&
<Table.Row>
<Table.HeaderCell colSpan={this.props.tableCols}>
<Menu floated='right' pagination>
{this.renderPagination()}
</Menu>
</Table.HeaderCell>
</Table.Row>
}
</Table.Footer>
)
}
}
setState is batched and invoked asynchronously, meaning when you call to this.setState({page}) then read this.state.page in fetchCallBack you probably get the "old" page and not the new page.
Either pass the page directly to fetchCallBack
this.fetchCallBack(page)
And read the page from it and not directly from the state
Or call it as the second argument of setState which is a callback that react will invoke right after the state has been updated.
this.setState({ page }, this.fetchCallBack);
At the point fetchCallBack is called, this.state.page is not updated yet because setState is called asynchronously, that's why it's using the old value. Try this:
handlePageClick(page) {
this.setState({ page }, this.fetchCallBack);
}
The callback syntax allows you to run the function in the next iteration.

React: how to add a spinner after click, and change screen after the animation completes

I saw there are already answered questions on how to add spinners during fetch requests.
However what I need is to stop showing the animation when the animation completes. The animation completes after the timeout is reached.
Also I have a best practice question.
It's a good practice to empty the resources on componentWillUnmount and clear there the timeout. In the code below I clear the timeout in a if condition, because it has to stop as the height of the element reaches the right level.
Is that ok as I did it? If now, how should it look like to have the same functionality in the componentWillUnmount lifecycle phase?
Here is the animation Component:
class Thermometer extends Component {
state = {
termFill : 0
};
componentDidMount() {
const interval = setInterval(() => {
this.setState({
termFill: this.state.termFill + 10
});
if (this.state.termFill === 110) {
window.clearInterval(interval);
}
}, 200)
}
render() {
const styles = {
height: `${this.state.termFill}px`
};
if (this.state.termFill < 100) {
return (
<section>
<div id="therm-fill" style={styles} />
[MORE CODE - SHORTENED FOR EASIER READING]
)
}
};
And here is the Component that has to appear after the animation disappears.
The steps are like this:
A user enter and uses this tool
The user clicks "calculate"
The animation appears instead or on top of the tool
When the animation completes, the animation Component disappears and the tool
is once again visible with its updated state (results of the
calculation).
class DiagnoseTool extends Component {
state = {
[OTHER STATES REMOVED TO KEEP THE CODE SHORTER]
wasBtnClicked: false
};
[OTHER RADIO AND CHECKBOX HANDLERS REMOVED TO KEEP THE CODE SHORTER]
onButtonClick = e => {
e.preventDefault();
this.calculate();
this.setState({
wasBtnClicked: true
})
};
addResult = () => {
const resultColor = {
backgroundColor: "orange"
};
let theResult;
if (this..... [CODE REMOVED TO HAVE THE CODE SHORTER]
return theResult;
};
calculate = () => {
let counter = 0;
let radiocounter = 0;
Object.keys(this.state).filter(el => ['cough', 'nodes', 'temperature', 'tonsillarex'].includes(el)).forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++
} else if (this.state.radioAge === "age45") {
radiocounter--
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
})
}
this.setState({
points: counter + radiocounter
});
};
render() {
const {cough, nodes, temperature, tonsillarex, radioAge, wasBtnClicked} = this.state;
return (
<Container>
<BackArrow />
[JSX REMOVED TO KEEP THE CODE SHORTER]
<div className="resultbox">
{
(wasBtnClicked) && this.addResult()
}
</div>
</div>
[HERE IS THE BUTTON]
<button
style={{height: "40px", width: "150px", cursor:"pointer"}}
type="submit"
className="calculateBtn"
onClick={this.onButtonClick}
disabled={!radioAge}
>CALCULATE</button>
</Container>
Add a boolean to your state and set it to false, when the user clicks the button set it to true, after doing the calculation set it to false.
calculate = () => {
let counter = 0;
let radiocounter = 0;
this.setState({
isLoading: true // set is loading to true and show the spinner
})
Object.keys(this.state)
.filter(el =>
["cough", "nodes", "temperature", "tonsillarex"].includes(el)
)
.forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++;
} else if (this.state.radioAge === "age45") {
radiocounter--;
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
});
}
this.setState({
points: counter + radiocounter,
isLoading: false // set it to false and display the results of the calculation
});
};
Example
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
timer = null;
constructor() {
super();
this.state = {
result: '',
isLoading: false
};
}
showContent = () => { this.setState({ isLoading: false, result: `7 + 5 = ${7 + 5}` })}
calculate = () => {
this.setState({
isLoading: true,
result: ''
});
this.timer = setTimeout(this.showContent, 5000);
}
componentWillUnmount = () => {
clearTimeout(this.timer);
}
render() {
return (
<div>
<p>7 + 5</p>
<p>{this.state.result}</p>
{ this.state.isLoading
? <p>Calculating...</p>
: <button onClick={this.calculate}>Calculate</button>
}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>

Show an alert when a user stops typing in ReactJS

I want to show an alert message in ReactJS when the user stops typing in a a form field.
This can help you.
This kind of features are not React specific, so you can achieve that in many ways with JS.
Simple component :
class App extends Component {
typingTimer = null;
handleChange = (evt) => {
const val = evt.target.value;
clearTimeout(this.typingTimer);
this.typingTimer = setTimeout(() => {
if (val) {
window.alert('Stopped typing !');
}
}, 500);
}
componentWillUnmount() {
clearTimeout(this.typingTimer);
}
render() {
return (
<div>
<input onChange={this.handleChange} />
</div>
);
}
}
Here, I created React functional component for my use.
const App = () => {
let typingTimer = null;
const makeAnApiCall =(inputValue) => {
console.log("Making an Ajax Call");
window.alert('Making an Ajax Call');
}
const handleChange = (evt) => {
const val = evt.target.value;
clearTimeout(typingTimer);
typingTimer = setTimeout(() => {
if (val) {
makeAnApiCall(val)
}
}, 500);
}
useEffect(() => {
return () => {
clearTimeout(typingTimer);
}
}, [])
return (
<div>
<input type="text" onChange={handleChange} />
</div>
);
}
Workgin demo on stackblitz

Categories

Resources