Onchange event is called late as i quickly remove inputs in react - javascript

I have created this simple lorem ipsum generator, it works fine but when i input a number in it and quickly remove that number, there is still some output displayed, note that this does not happen when i remove number slowly
Generator.js
import React,{Component} from 'react';
import Output from './Output.js';
class Generator extends Component{
state = {
sentence: 0,
para: 0,
outputsentence:'',
outputpara:'',
}
sentenceHandler = async (e) => {
await this.setState({sentence: e.target.value});
if(this.state.sentence === '0' || this.state.sentence === '')
{
this.setState({outputsentence:''});
}
else{
try{
let sentence = await fetch(`https://baconipsum.com/api/?type=all-meat&sentences=${this.state.sentence}&start-with-lorem=1`);
sentence = await sentence.json();
this.setState({outputsentence:sentence});
} catch(e)
{
console.log(e);
}
}
}
paraHandler = async (e) => {
await this.setState({para: e.target.value});
if(this.state.para === '0' || this.state.para === '')
{
this.setState({outputpara:''});
}
else{
try{
console.log(e.target.value);
let paragraph = await fetch(`https://baconipsum.com/api/?type=all-meat&paras=${this.state.para}&start-with-lorem=1`);
paragraph = await paragraph.json();
this.setState({outputpara:paragraph});
} catch(e)
{
console.log(e);
}
}
}
render()
{
return(
<div className='tc'>
<h3>Number of sentences : </h3>
<input type="number" onChange={this.sentenceHandler}/>
<Output op={this.state.outputsentence} />
<h3>Number of paras : </h3>
<input type="number" onChange={this.paraHandler}/>
<Output op={this.state.outputpara} />
</div>
)
}
}
export default Generator;
Output.js
import React from 'react';
const Output = ({op}) => {
return(
<div className='tc'>
<p>
{op}
</p>
</div>
)
}
export default Output;
It works fine but suppose i enter a number 12 and quickly remove it using backspaces then still it shows 1 line or para and this does not happen if i remove it one letter that is first 2 then 1 slowly.
see it on
https://wizardly-torvalds-092022.netlify.com/

You have race conditions. Because the asynchronous fetch/display operation has no cancellation mechanism in place, nothing's going to stop the operation from completing after you start another operation (by changing the output to something else).
There are probably some good libraries to deal with this cleanly, but here's how you could go about doing it manually. You'll need the concept of a "request ID" so you know whether the currently-running operation is the latest request versus something you should cancel.
class Generator extends Component {
state = {
sentence: 0,
outputsentence: '',
currentSentenceRequestId: null,
para: 0,
outputpara:'',
currentParaRequestId: null,
}
sentenceHandler = async (e) => {
var requestId = this.state.currentSentenceRequestId++;
await this.setState({
sentence: e.target.value,
currentSentenceRequestId: requestId
});
if(this.state.sentence === '0' || this.state.sentence === '') {
this.setState({ outputsentence: '' });
} else{
try {
let sentence = await fetch(`https://baconipsum.com/api/?type=all-meat&sentences=${this.state.sentence}&start-with-lorem=1`);
sentence = await sentence.json();
// Ignore if a newer request has come in
if (this.state.currentSentenceRequestId !== requestId) {
return;
}
this.setState({
outputsentence: sentence
});
} catch(e) {
console.log(e);
} finally {
this.setState({
currentSentenceRequestId: null
});
}
}
}
// ...
}

Related

React JS - stop infinite scroll at the end of array

I have this simple react app, where I fetch the Flickr public feed. So, I can scroll to the end of the page and new content is going to show. So I would like to scroll until there is nothing else new, and the app stops trying to load more content, because it has reached the last item of the list, which is not happening if I try to scroll (you can see that on the loading message). How can I fix this?
Check the code below:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import $ from "jquery";
import PhotoListItem from "./photoListItem";
import "./photoApp.css";
export default class PhotoApp extends Component {
constructor(props) {
super(props);
this.state = {
photoList: [],
searchTerm: "cyanotype",
items: 8,
loadingState: false,
loadingMessage: ""
};
}
componentDidMount() {
this.getPhotoList();
this.onInfiniteScroll();
}
/* get data from Flickr public feed */
getPhotoList = () => {
const flickrApiPoint =
"https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?&tags=" +
this.state.searchTerm;
try {
$.ajax({
url: flickrApiPoint,
dataType: "jsonp",
data: { format: "json" },
success: function(data) {
this.setState({ photoList: data.items });
}.bind(this)
});
} catch (err) {
console.log(err);
}
};
/* code for infinite scroll */
onInfiniteScroll = () => {
this.refs.iScroll.addEventListener("scroll", () => {
if (
this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=
this.refs.iScroll.scrollHeight - 20
) {
this.loadMoreItems();
}
});
};
/* code for infinite scroll */
loadMoreItems = () => {
if (this.state.loadingState) {
return;
}
this.setState({
loadingState: true,
loadingMessage: "Loading photos..."
});
setTimeout(() => {
this.setState(prevState => ({
items: prevState.items + 8,
loadingState: false,
loadingMessage: "No more photos to show."
}));
}, 1000);
};
render() {
console.log(this.state.photoList)
return (
<div className="appContainer" ref="iScroll">
<div className="appHeader">
<h1 className="headerTitle">
Welcome to Flickr Alternative Photography Feed!
</h1>
</div>
<div className="gridContainer">
{this.state.photoList
.slice(0, this.state.items)
.map((photo, index) => {
const author = photo.author.split(/"/)[1];
const authorLink = photo.description.split(/"/)[1];
const description = photo.description.split(/"/)[13];
return (
<PhotoListItem
key={index}
url={photo.media.m}
photoLink={photo.link}
title={photo.title}
author={author}
authorLink={authorLink}
description={description}
tags={photo.tags}
/>
);
})}
</div>
<React.Fragment>
{this.state.loadingState ? (
<p className="loading">{this.state.loadingMessage}</p>
) : (
<p className="loading">{this.state.loadingMessage}</p>
)}
</React.Fragment>
</div>
);
}
}
LIVE EXAMPLE HERE
Thank you!
You could check if the item that you've loaded exceeds your items in your ajax request
/* code for infinite scroll */
loadMoreItems = () => {
// hasMore = data.items.length (you may want to rename this more appropriately)
if (this.state.loadingState || (this.state.items > this.state.hasMore)) {
// Do not load if there's no more items
return;
}
...
Your onInfiniteScroll doesn't have any code right now that checks whether it should load more items, it just blindly does. So: at the end of getPhotoList you probably want to check whether that's the last page of results and if so, do a setState({ exhausted: true }) or something similar, so you can check that value in your onInfiniteScroll and not do anything if you see this.state.exhausted === true.

React search and filter methods issue

I'm working on a component that should be able to:
Search by input - Using the input field a function will be called after the onBlur event got triggered. After the onBlur event the startSearch() method will run.
Filter by a selected genre - From an other component the user can select a genre from a list with genres. After the onClick event the startFilter() method will run.
GOOD NEWS:
I got the 2 functions above working.
BAD NEWS:
The above 2 functions don't work correct. Please see the code underneath. The 2 calls underneath work, but only if I comment one of the 2 out. I tried to tweak the startSearch() method in various ways, but I just keep walking to a big fat wall.
//////Searching works
//////this.filter(this.state.searchInput);
//Filtering works
this.startFilter(this.state.searchInput);
QUESTION
How can I get the filter/search method working?. Unfortunately simply putting them in an if/else is not the solution (see comments in the code).
import { Component } from 'preact';
import listData from '../../assets/data.json';
import { Link } from 'preact-router/match';
import style from './style';
export default class List extends Component {
state = {
selectedStreamUrl: "",
searchInput: "",
showDeleteButton: false,
searchByGenre: false,
list: [],
}
startFilter(input, filterByGenre) {
this.setState({
searchByGenre: true,
searchInput: input,
showDeleteButton: true
});
alert("startFilter  ")
console.log(this.state.searchByGenre)
/////////---------------------------------
document.getElementById("searchField").disabled = false;
document.getElementById('searchField').value = input
document.getElementById('searchField').focus()
// document.getElementById('searchField').blur()
document.getElementById("searchField").disabled = true;
console.log(input)
this.filter(input);
}
//search
startSearch(input) {
alert("startSearch  ")
console.log(this.state.searchByGenre)
//komt uit render()
if (!this.state.searchByGenre) {
//check for input
this.setState({
searchInput: input.target.value,
showDeleteButton: true,
})
//Searching works
//this.filter(this.state.searchInput);
//Filtering works
this.startFilter(this.state.searchInput);
// DOESNT WORK:
// if (this.state.searchInput != "") {
// this.filter(this.state.searchInput);
// } else {
// this.startFilter(this.state.searchInput);
// }
}
}
setAllLists(allLists) {
console.log("setAllLists")
console.log(this.state.searchByGenre)
this.setState({ list: allLists })
//document.body.style.backgroundColor = "red";  
}
filter(input) {
let corresondingGenre = [];
let filteredLists = listData.filter(
(item1) => {
var test;
if (this.state.searchByGenre) {
alert("--this.state.searchByGenre")
//filterByGenre
//& item1.properties.genre == input
for (var i = 0; i < item1.properties.genre.length; i++) {
if (item1.properties.genre[i].includes(input)) {
corresondingGenre.push(item1);
test = item1.properties.genre[i].indexOf(input) !== -1;
return test;
}
this.setState({ list: corresondingGenre })
}
} else {
//searchByTitle
alert("--default")
test = item1.title.indexOf(input.charAt(0).toUpperCase()) !== -1;
}
return test;
})
console.log("filterdLists:")
console.log(filteredLists)
console.log("corresondingGenre:")
console.log(corresondingGenre)
//alert(JSON.stringify(filteredLists))
this.setState({ list: filteredLists })
}
removeInput() {
console.log("removeInput    ")
console.log(this.state.searchByGenre)
this.setState({ searchInput: "", showDeleteButton: false, searchByGenre: false })
document.getElementById("searchField").disabled = false;
this.filter(this.state.searchInput)
}
render() {
//alle 's komen in deze array, zodat ze gefilterd kunnen worden OBV title.
if (this.state.list === undefined || this.state.list.length == 0 && this.state.searchInput == "") {
//init list
console.log("render ")
console.log(this.state.searchByGenre)
this.filter(this.state.searchInput)
}
return (
<div class={style.list_container}>
<input class={style.searchBar} type="text" id="searchField" placeholder={this.state.searchInput} onBlur={this.startSearch.bind(this)} ></input>
{
this.state.searchByGenre ?
<h1>ja</h1>
:
<h1>nee</h1>
}
{
this.state.showDeleteButton ?
<button class={style.deleteButton} onClick={() => this.removeInput()}>Remove</button>
: null
}
{
this.state.list.map((item, index) => {
return <div>
<p>{item.title}</p>
</div>
})
}
</div>
);
}
}
SetState is an async operation that takes a callback function. I suspect that your second function runs before the first SetState is finished.
Also, you are modifying the DOM yourself. You need to let React do that for you just by modifying state. I don't have time to write up an example now, but hopefully this helps in the meantime.
can you modify your search func,
//search
startSearch(input) {
const { value } = input.target
const { searchInput } = this.state
if (!this.state.searchByGenre) {
this.setState(prevState => ({
searchInput: prevState.searchInput = value,
showDeleteButton: prevState.showDeleteButton = true,
}))
JSON.stringify(value) !== '' ? this.filter(value) : this.startFilter(searchInput)
}
}

react-native Native element detection

I want in my react-native app detect if user have a touch ID sensor or not and then if he have I want to display button with native element action instead of just normal action button. When I created if statement it shows me an error. I'm using 'create-react-native-app' with expo client SDK.
error message
Code
class LoginButton extends React.Component {
state = {
waiting: false,
};
render() {
let authFunction;
if (NativeModules.ExponentFingerprint.hasHardwareAsync() === true) {
if (Platform.OS === 'android') {
authFunction = async () => {
this.setState({waiting: true});
try {
let result = await NativeModules.ExponentFingerprint.authenticateAsync();
if (result.success) {
alert('Udało Ci się zalogować')
} else {
alert('Logowanie nie udane')
}
}
finally {
this.setState({waiting: false})
}
};
} else if (Platform.OS === 'ios') {
authFunction = async () => {
let result = await NativeModules.ExponentFingerprint.authenticateAsync(
'Zaloguj się przy użyciu TouchID'
);
if (result.success) {
alert('Udało Ci się zalogować')
} else {
alert('Logowanie nie udane')
}
};
}
return (
<Button onPress={authFunction} title="Zaloguj się przy użyciu odcisku palca" style={styles.buttonStyle}>
{this.state.waiting
? 'Czekam na TouchID...'
: 'Zalogowano przy użyciu TouchID'}
</Button>
)
} else if (NativeModules.ExponentFingerprint.hasHardwareAsync() === false) {
return (
<Button onPress={} title="Zaloguj się" style={styles.buttonStyle}/>
)
}
}
}
The issue is here
<Button
onPress={} <--- here
title="Zaloguj się"
style={styles.buttonStyle}
/>
React doesn't allow you to assign empty expressions to JSX attributes.
In order to fix it, just remove it
<Button title="Zaloguj się" style={styles.buttonStyle}/>
or assign it, for example, to authFunction which will be null.
<Button onPress={authFunction} title="Zaloguj się" style={styles.buttonStyle}/>

ReactJS - How to allow user to type only integers

I'm trying to make if else statement to allow user to type only integer in TextArea but i have no idea how.
This is my code:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
currencyValue: '',
currencyCode: 'USD',
result: ''
}
}
handleChangeCurrencyValue(event) {
console.log('qwer ' + this)
this.setState(Object.assign({}, this.state, {currencyValue: event.target.value}))
}
handleChangeCurrencyCode(event) {
console.log('qwer ' + this)
this.setState(Object.assign({}, this.state, {currencyCode: event.target.value}))
}
calculate(event){
var obj = this
axios.get('http://localhost:8080/Currency/currency?currencyCode=' + this.state.currencyCode + '&currencyValue=' + this.state.currencyValue + '&responseType=json')
.then(function(resp){
console.log('RESULT: ', resp.data.result)
obj.setState(Object.assign({}, obj.state, {result: resp.data.result}))
})
.catch(error => {
console.log(error)
});
}
render() {
return (
<div>
<TextArea functionChangeValue={this.handleChangeCurrencyValue.bind(this)} value={this.state.currencyValue} />
<ComboBox functionChangeCode={this.handleChangeCurrencyCode.bind(this)} value={this.state.currencyCode} />
<p>
<button onClick={this.calculate.bind(this)}>Calculate</button>
</p>
<div>{this.state.result}</div>
</div>
);
}
}
class TextArea extends React.Component {
render() {
return (
<div>
<h4>
<div>Type value you want to exchange: </div>
<input type='text' onChange={this.props.functionChangeValue}/>
{/* <div>Value of your currency is: {this.props.value}</div> */}
</h4>
</div>
);
}
}
class ComboBox extends React.Component {
render() {
return(
<div>
<h4>
<div>Select your currency:</div>
<select onChange={this.props.functionChangeCode}>
<option>USD</option>
<option>EUR</option>
<option>GBP</option>
</select>
{/* <div>Your currency is: {this.props.value}</div> */}
</h4>
</div>
);
}
}
export default App;
Can you give me idea or some example?
You can use regex and conditionally change state.
onChange(event){
const regex = /^[0-9\b]+$/;
const value = event.target.value;
if (value === '' || regex.test(value)) {
this.setState({ value })
}
}
Consider making use of the ValidatorJS Library as opposed to rolling your own validation method.
Example usage in your code would be something like this...
handleChangeCurrencyValue(event) {
console.log('qwer ' + this)
const currencyValue = event.target.value;
if(validator.isInt(currencyValue)) {
this.setState({ currencyValue });
}
}
I recommend using libraries that have been thoroughly tested for validation as this will reduce security concerns down the line. Also, validator is extremely easy to implement.
can it be something like that:
<input type="number" min="0" step="1"/>
You can use regex (regular expression) to handle that. The regex that you use is /[0-9]+/g.
For example you have input:
<input value={this.state.value} onChange={this.onChange}/>
And the onChange:
onChange(text) {
let newText = '';
const numbers = '^[0-9]';
for (let i = 0; i < text.length; i++) {
if (numbers.indexOf(text[i] > -1)) {
newText += text[i];
}
this.setState({ currencyValue: newText });
}
}
or like this:
onChange(e){
const re = /^[0-9\b]+$/;
if (e.target.value == '' || re.test(e.target.value)) {
this.setState({currencyValue: e.target.value})
}
}
May it can help you! :)

SetState causing App crash in react.js

I have recently started working on react.js, while creating the login page I have used setstate method to set the value of userEmail to text box.
I have created a method which checks the validity of email address and I am calling it every time when user enters a new letter.
handleChangeInEmail(event) {
var value = event.target.value;
console.log("change in email value" + value);
if(validateEmailAddress(value) == true) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
This method and userEmailForLogin state is passed in render method as
<EmailLoginPage
userEmailForLogin = {this.state.userEmailForLogin}
onHandleChangeInEmail= {this.handleChangeInEmail}
/>
I am using the method to validate the email address and the method is
validateEmailAddress : function(emailForLogin) {
if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(emailForLogin)) {
return true;
}
return false;
},
I am using this method and state in render of EmailLoginPage as <input type="text" name="" placeholder="Enter Email" className="email-input-txt" onChange={props.onHandleChangeInEmail} value = {props.userEmailForLogin}/>
This is working fine in normal case , but when I try to input a large email addess say yjgykgkykhhkuhkjhgkghjkhgkjhghjkghjghghkghbghbg#gmail.com, it crashes
IMO the frequent change in state is causing this but I couldn't understand what should be done to get rid of this.
I think issue is with the regex only, i tried with other and it's working properly.
Instead of writing the if/else inside change function simply you are write it like this:
change(event) {
var value = event.target.value;
this.setState({
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
});
}
Copied the regex from this answer: How to validate email address in JavaScript?
Check the working solution:
class App extends React.Component {
constructor(){
super();
this.state = {
value: '',
showInvalidEmailError: false
}
this.change = this.change.bind(this);
}
change(event) {
var value = event.target.value;
this.setState(function() {
return {
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
}
});
}
validateEmailAddress(emailForLogin) {
var regex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(regex.test(emailForLogin)){
return true;
}
return false;
}
render() {
return(
<div>
<input value={this.state.value} onChange={this.change}/>
<br/>
valid email: {this.state.showInvalidEmailError + ''}
</div>
);
}
}
ReactDOM.render(
<App/>,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
You could use Lodash's debounce function so that the check function is not called unless the user stops typing for x amount of time (300ms in my scenario below).
_this.debounceCheck = debounce((value) => {
if(validateEmailAddress(value)) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
}, 300)
handleChangeInEmail(event) {
_this.debounce(event.target.value)
}
A solution using debounce. This way multiple setState can be reduced.
DEMO: https://jsfiddle.net/vedp/kp04015o/6/
class Email extends React.Component {
constructor (props) {
super(props)
this.state = { email: "" }
}
handleChange = debounce((e) => {
this.setState({ email: e.target.value })
}, 1000)
render() {
return (
<div className="widget">
<p>{this.state.email}</p>
<input onChange={this.handleChange} />
</div>
)
}
}
React.render(<Email/>, document.getElementById('container'));
function debounce(callback, wait, context = this) {
let timeout = null
let callbackArgs = null
const later = () => callback.apply(context, callbackArgs)
return function() {
callbackArgs = arguments
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}

Categories