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
Related
I read this tutorial and try to make an auto complete text box.
Here is part of the code:
import React, {useState} from 'react';
import {SearchInput, SearchUL, SearchLI} from './index.style';
// based on this: https://programmingwithmosh.com/react/simple-react-autocomplete-component/
const Autocomplete = ({suggestions = []}) => {
const [activeSuggestion, setActiveSuggestion] = useState(0);
const [filteredSuggestions, setFilteredSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [userInput, setUserInput] = useState('');
const [isTyping, setIsTyping] = useState(false);
// timer id
let typingTimer;
//
let doneTypingInterval = 1000;
// on change
const onChange = e => {
// input
const userInput = e.currentTarget.value;
// match
const filteredSuggestions = suggestions.filter(
suggestion =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
// set state
setActiveSuggestion(0);
setFilteredSuggestions(filteredSuggestions);
setShowSuggestions(true);
setUserInput(e.currentTarget.value);
};
// onclick
const onClick = e => {
// set state
setActiveSuggestion(0);
setFilteredSuggestions([]);
setShowSuggestions(false);
setUserInput(e.currentTarget.innerText); //?
console.log('on click');
};
// done
const doneTyping = () => {
console.log('done type');
setIsTyping(false);
};
// key down
const onKeyDown = e => {
if (e.keyCode === 13) {
// 1. enter key
// state
setActiveSuggestion(0);
setShowSuggestions(false);
setUserInput(filteredSuggestions[activeSuggestion]);
} else if (e.keyCode === 38) {
// 2. up arrow key
// no active, out
if (activeSuggestion === 0) {
return;
}
// go up, -1
setActiveSuggestion(activeSuggestion - 1);
} else if (e.keyCode === 40) {
// 3. down arr
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
// go down +1
setActiveSuggestion(activeSuggestion + 1);
} else {
}
};
// key up
const onKeyUp = e => {
// clear old timer
clearTimeout(typingTimer);
// has val, then clean up
if (e.currentTarget.value) {
setIsTyping(true);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
}
};
// tmp: sugList
let suggestionsListComponent;
// state: show and u-input
if (showSuggestions && userInput) {
// state: filterSug.len
if (filteredSuggestions.length) {
// ul + li
suggestionsListComponent = (
<SearchUL>
{filteredSuggestions.map((suggestion, index) => {
return (
<SearchLI key={suggestion} onClick={onClick}>
{suggestion}
</SearchLI>
);
})}
</SearchUL>
);
} else {
// no
suggestionsListComponent = (
<div>
<em>No suggestions!</em>
</div>
);
}
}
// --------
// NOTE: this makes the drop down list disappear, but not able to click the list
// --------
const onBlur = () => {
setShowSuggestions(false);
};
return (
<>
<SearchInput
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
value={userInput}
isTyping={isTyping}
//onBlur={onBlur}
/>
{suggestionsListComponent}
</>
);
};
export default Autocomplete;
Sorry, proxy not allow me to upload image. In summary, when you start typing, if match, there will be a dropdown list. Click on 1 of the item, the search box will be filled with it.
Now image you type half way, then your mouse moves to other areas, so like onBlur, the dropdown list is not able to disappear.
I tried to make a onBlur function
const onBlur = () => {
setShowSuggestions(false);
};
and have something like this:
return (
<>
<SearchInput
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
value={userInput}
isTyping={isTyping}
//onBlur={onBlur}
/>
{suggestionsListComponent}
</>
);
This time, if I click other areas, the dropdown list is able to hide, but when I do normal typing and select dropdown item, the selected item is not able to go to the search box.
Full code here
I have created a sandbox link for you. Check this: https://codesandbox.io/s/testformik-xgl3w
In this, I have wrapped your component with div and passed a ref. And on componentDidMount I am listening to click on the document and calling the function and if the target clicked is different than the AutoComplete components ( input and suggestion ) I close the suggestion box.
This is the new code:
import React, { useState, useEffect } from "react";
import { SearchInput, SearchUL, SearchLI } from "./index.style";
const inputRef = React.createRef();
// based on this: https://programmingwithmosh.com/react/simple-react-autocomplete-component/
const Autocomplete = ({ suggestions = [] }) => {
const [activeSuggestion, setActiveSuggestion] = useState(0);
const [filteredSuggestions, setFilteredSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [userInput, setUserInput] = useState("");
const [isTyping, setIsTyping] = useState(false);
const handleOuterClick = e => {
if (!inputRef.current.contains(e.target)) {
setShowSuggestions(false);
}
};
useEffect(() => {
document.addEventListener("click", handleOuterClick);
}, []);
// timer id
let typingTimer;
//
let doneTypingInterval = 1000;
// on change
const onChange = e => {
// input
const userInput = e.currentTarget.value;
// match
const filteredSuggestions = suggestions.filter(
suggestion =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
// set state
setActiveSuggestion(0);
setFilteredSuggestions(filteredSuggestions);
setShowSuggestions(true);
setUserInput(e.currentTarget.value);
};
// onclick
const onClick = e => {
// set state
setActiveSuggestion(0);
setFilteredSuggestions([]);
setShowSuggestions(false);
setUserInput(e.currentTarget.innerText); //?
console.log("on click");
};
// done
const doneTyping = () => {
console.log("done type");
setIsTyping(false);
};
// key down
const onKeyDown = e => {
if (e.keyCode === 13) {
// 1. enter key
// state
setActiveSuggestion(0);
setShowSuggestions(false);
setUserInput(filteredSuggestions[activeSuggestion]);
} else if (e.keyCode === 38) {
// 2. up arrow key
// no active, out
if (activeSuggestion === 0) {
return;
}
// go up, -1
setActiveSuggestion(activeSuggestion - 1);
} else if (e.keyCode === 40) {
// 3. down arr
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
// go down +1
setActiveSuggestion(activeSuggestion + 1);
} else {
}
};
// key up
const onKeyUp = e => {
// clear old timer
clearTimeout(typingTimer);
// has val, then clean up
if (e.currentTarget.value) {
setIsTyping(true);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
}
};
// tmp: sugList
let suggestionsListComponent;
// state: show and u-input
if (showSuggestions && userInput) {
// state: filterSug.len
if (filteredSuggestions.length) {
// ul + li
suggestionsListComponent = (
<SearchUL>
{filteredSuggestions.map((suggestion, index) => {
return (
<SearchLI key={suggestion} onClick={onClick}>
{suggestion}
</SearchLI>
);
})}
</SearchUL>
);
} else {
// no
suggestionsListComponent = (
<div>
<em>No suggestions!</em>
</div>
);
}
}
return (
<div ref={inputRef}>
<SearchInput
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
value={userInput}
isTyping={isTyping}
/>
{suggestionsListComponent}
</div>
);
};
export default Autocomplete;
Hope this helps!
Update your onBlur function using timer, like this:
const onBlurr = () => {
setTimeout(() => setShowSuggestions(false), 500);
};
I am just getting acquainted with testing and I have a little problem. I'm trying to test the functions of a component that change state and call functions of a document. In the documentation of Jest and Enzyme, I did not find the right example. Here is a sample code:
class EditableText extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isEditing: false,
currentValue: null
};
this.textAreaRef = React.createRef();
}
onClick = () => {
if (!this.state.isEditing) {
this.setState({ isEditing: true });
setTimeout(() => {
this.textAreaRef.current.focus();
}, 100);
document.addEventListener('keydown', this.onKeyDown);
document.addEventListener('click', this.onOutsideClick);
}
};
onOutsideClick = e => {
if (e.target.value !== this.state.currentValue) {
this.closeEditMode();
}
};
closeEditMode() {
this.setState({ isEditing: false });
document.removeEventListener('keydown', this.onKeyDown);
document.removeEventListener('click', this.onOutsideClick);
}
onKeyDown = e => {
if (e.code === 'Enter') {
this.onUpdateValue();
} else if (e.code === 'Escape') {
this.closeEditMode();
}
};
onUpdateValue = () => {
this.props.onSubmit(this.state.currentValue || this.props.value);
this.closeEditMode();
};
render() {
const { isEditing, currentValue } = this.state;
const { value } = this.props;
return isEditing ? (
<TextArea
className="editable-text__textarea"
ref={this.textAreaRef}
onClick={this.onClick}
value={currentValue === null ? value || '' : currentValue}
onChange={(_e, { value }) => this.setState({ currentValue: value })}
/>
) : (
<div className="editable-text__readonly" onClick={this.onClick}>
<div>{this.props.children}</div>
<div className="editable-text__icon-container">
<Icon name="edit" className="editable-text__icon" />
</div>
</div>
);
}
}
How to test functions, that receive and change state and calls document.addEventListener? Please, help.
UPDATE > Tests i already wrote:
describe('tests of component', () => {
test('Component renders correctly with isEditing=false', () => {
const component = renderer
.create(<EditableText children="I'm text in EditableText" />)
.toJSON();
expect(component).toMatchSnapshot();
});
test('Component changes to isEditing=true when clicked on it', () => {
const component = renderer
.create(<EditableText />);
let tree = component.toJSON();
tree.props.onClick();
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('tests of logic', () => {
test.only('OnClick should change state and add event listeners', ()=> {
const state = { isEditing: false, currentValue: null }
global.document.addEventListener = jest.fn();
EditableText.onClick(() => {
expect(global.document.addEventListener).toHaveBeenCalled();
});;
});
});
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
I need to check the user input value using React.js but its not working as expected. I am explaining my code below.
import React, { Component } from "react";
import TodoItems from "./TodoItems";
import "./TodoList.css";
class TodoList extends Component {
constructor(props, context){
super(props, context);
this.state={
items:[]
}
this.listOfKeyWords = ["script", "embed", "object", "iframe"];
this.addItem=this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.editItem = this.editItem.bind(this);
}
addItem(e){
e.preventDefault();
if(this.state.editKey){
this.saveEditedText();
return;
}
let maliciousStr = this.listOfKeyWords.find(tag => {
return this.inputElement.value.toLowerCase().indexOf(tag) !== -1;
});
if (!maliciousStr) {
var itemArray = this.state.items;
if (this.inputElement.value !== '') {
itemArray.unshift({
text:this.inputElement.value,
key:Date.now()
})
this.setState({
items:itemArray
})
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has added successfully</p>');
this.inputElement.value='';
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
}, 3000);
}
}else{
this.inputElement.value='';
}
}
deleteItem(key) {
const result = window.confirm('Are you sure to delete this item');
if (result) {
var filteredItems = this.state.items.filter(function (item) {
return (item.key !== key);
});
this.setState({
items: filteredItems
});
}
}
editItem(key){
this.state.items.map(item =>{
if (item.key==key) {
this.inputElement.value=item.text;
}
})
this.setState({editKey: key});
}
saveEditedText(){
let value = this.inputElement.value;
this.setState(prevState => ({
items: prevState.items.map(el => {
if(el.key == prevState.editKey)
return Object.assign({}, el, {text: value});
return el;
}),
editKey: ''
}));
let maliciousStr = this.listOfKeyWords.find(tag => {
return this.inputElement.value.toLowerCase().indexOf(tag) !== -1;
});
if (!maliciousStr) {
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has updated successfully</p>');
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
}, 3000);
this.inputElement.value='';
}else{
this.inputElement.value='';
}
}
render() {
return (
<div className="todoListMain">
<div className="header" id="parentDiv">
<div className="pageHeading" dangerouslySetInnerHTML={{ __html: "Todo Demo Application" }}></div>
<div className="wrapper">
<div ref={divEl => {
this.divRef = divEl;
}}></div>
<form onSubmit={this.addItem}>
<input ref={(a)=>this.inputElement=a} placeholder="enter task">
</input>
<button type="submit">{this.state.editKey? "Update": "Add"}</button>
</form>
<TodoItems entries={this.state.items} delete={this.deleteItem} edit={this.editItem}/>
</div>
</div>
</div>
);
}
}
export default TodoList;
Here I am adding data and after submitting the message is displaying on the top. Here I have one validation if user input has value like <script>Hii</script> the the message will not display or the value can not be added but in my coding its not happening like this. I need if <script>Hii</script> is there those will be filtered out.
I want to have a dynamic number of input field and also able to remove it. I can do it if this is only client side but I am getting the data from server and the issue is that I am not getting the id from the server, and that I need to be able to change the number of fields on the fly in the UI.
since I am also getting some fields from the server the manually added id was not longer in sync with the # of items.
How do i achieve this?
Here is my code
let _id = 0;
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}
return s4() + s4();
}
class Rules extends React.PureComponent {
constructor(props, context) {
super(props, context);
this.state = {
rules: {
rule_regulations: {}
},
numberOfInput: []
};
}
componentDidMount() {
this.props.loadRules();
}
componentWillReceiveProps(nextProps) {
if (nextProps.rules.size && nextProps.rules !== this.props.rules) {
nextProps.rules
.entrySeq()
.map(([key, value]) => {
this.setState(state => ({
rules: {
...state.rules,
rule_regulations: {
...state.rules.rule_regulations,
[key]: value
}
},
numberOfInput: [...state.numberOfInput, guid()]
}));
})
.toArray();
}
}
handleChange = e => {
const { value, name } = e.target;
this.setState({
rules: {
...this.state.rules,
rule_regulations: {
...this.state.rules.rule_regulations,
[name]: value
}
}
});
};
handleAddRules = e => {
this.setState({
numberOfInput: [...this.state.numberOfInput, guid()]
});
};
handleRemoveRules = (e, num) => {
e.preventDefault();
this.setState({
numberOfInput: this.state.numberOfInput.filter(input => input !== num)
});
};
handleSave = e => {
e.preventDefault();
const obj = {
rule_regulations: Object.values(this.state.rules.rule_regulations)
};
this.props.postRules(obj);
};
render() {
const { numberOfInput } = this.state;
return (
<div className="basic-property">
<span className="circleInputUi" onClick={this.handleAddRules}>
+
</span>
<RulesInputContainer
numberOfInput={numberOfInput}
handleChange={this.handleChange}
handleRemoveRules={this.handleRemoveRules}
handleSave={this.handleSave}
value={this.state.rules.rule_regulations}
/>
</div>
);
}
}
const RulesInputContainer = props => {
return (
<div className="rules-input">
{props.value &&
Object.keys(props.value).map(key =>
<RulesInput
key={key}
num={key}
value={props.value}
handleChange={props.handleChange}
handleRemoveRules={props.handleRemoveRules}
/>
)}
<button className="button" onClick={props.handleSave}>
Save
</button>
</div>
);
};
export default RulesInputContainer;
const RulesInput = props => {
return (
<form className="form">
<InputField
label={`Rule ${props.num + 1}`}
type="text"
name={`${props.num}`}
value={props.value[props.num] || ""}
onChange={props.handleChange}
/>
<Button onClick={e => props.handleRemoveRules(e, props.num)}>
Remove
</Button>
</form>
)
}
I want the synchronization with server data and client
You can store returned data from your server into your component state.
componentDidMount() {
this.loadRules();
}
loadRules() {
let rules = this.props.loadRules();
this.setState({rules});
}
You can keep the synchronization by setting your state with data you received from your server.
In addition, it's not related to your question. Since RulesInputContainer and RulesInput are presentational components I think you can combine those into 1 component.