my component look like this
const onkeychange = async (e) => {
setState({ ...state, textValue: e.target.value })
if (delay !== false) {
clearTimeout(delay)
}
let string = e.target.value
if (string.trim() == '') {
setState({ ...state, issearching: false })
}
}
<input
type="text"
autoComplete="off"
name="search"
placeholder="Search for a Product, Brand or Category"
value={state.textValue}
className="search"
onChange={onkeychange}
/>
problem is, When after I type something in the input. Then try to delete everything, I can't I can delete all but one last character. For exmaple: I type hello in the input. trying to delete it with backspace, I can remove ello but not the h
You're checking if string === '' and case true you're setting the state using the old state which includes string's value just before deleting the last char, this is why you can't delete the last one. You should remove the condition
const onkeychange = async (e) => {
setState({ ...state, textValue: e.target.value })
if (delay !== false)
clearTimeout(delay)
}
Use functional form of setState to get the latest state when updating it. Also I'd recommend to consider using reducer hook here.
const onkeychange = async e => {
const value = e.target ? e.target.value : "";
setState(currentState => ({ ...currentState, textValue: value }));
if (delay !== false) {
clearTimeout(delay)
}
if (value.trim() === "") {
setState(currentState => ({ ...currentState, issearching: false }));
}
};
The setState is async, you can use a callback in the setState to get the correct preState to solve this problem.
const string = e.target.value;
setState((prevState) => {
return { ...prevState, textValue: string }
})
if (delay !== false) {
clearTimeout(delay)
}
if (string.trim() === '') {
setState((prevState) => {
return ({ ...prevState, isSearching: false })
})
}
or a more simple way is to only run setState once.
const string = e.target.value
if (delay !== false) {
clearTimeout(delay);
}
const isSearching = string.trim() !== "";
setState({ ...state, textValue: string, isSearching});
view demo on code sandbox
Related
I'm new to React and I'm currently learning about useReducer.
I've created a simple login feature that verifies if the user inputted email includes '#' and the password length is greater than 5.
If these two conditions are met, I want my program to display an alert with success or fail message when pressing on the submit button.
What I'm curious about is that the application displays "Success" on submit when I add dispatch({type: 'isCredValid')} in useEffect(commented out in the code below), but the application displays "fail" when I add the dispatch({type: 'isCredValid'}) in the onSubmit handler without using useEffect. I was expecting the application to display "Success" when adding the dispatch({type: 'isCredValid')} in the onSubmit handler without the help of useEffect. Why is it not displaying "Success"? And why does my application display "Success" when the dispatch function is in the useEffect?
Reducer function :
const credReducer = (state, action) => {
switch(action.type) {
case 'email' :
return {...state, email: action.value, isEmailValid: action.value.includes('#')};
case 'password' :
return {...state, password: action.value, isPasswordValid: action.value.length > 5 ? true : false};
case 'isCredValid' :
return {...state, isCredValid: state.isEmailValid && state.isPasswordValid ? true : false};
default :
return state;
}
}
Component and input handlers
const Login = () => {
const [credentials, dispatch] = useReducer(credReducer, {
email: '',
password: '',
isEmailValid: false,
isPasswordValid: false,
isCredValid: false
})
// useEffect(() => {
// dispatch({type: 'isCredValid'})
// }, [credentials.isEmailValid, credentials.isPasswordValid])
const handleSubmit = (e) => {
e.preventDefault()
dispatch({ type: "isCredValid" })
if (credentials.isCredValid === true) {
alert ("Success!")
} else {
alert ('failed')
}
}
const handleEmail = (e) => {
dispatch({ type: "email", value: e.target.value })
}
const handlePassword = (e) => {
dispatch({ type: "password", value: e.target.value })
}
return (
<Card className={classes.card}>
<h1> Login </h1>
<form onSubmit={handleSubmit}>
<label>Email</label>
<input type="text" value={credentials.email} onChange={handleEmail}/>
<label>Password</label>
<input type="text" value={credentials.password} onChange={handlePassword}/>
<button type="submit"> Submit </button>
</form>
</Card>
)
}
if (credentials.isCredValid === true) {
alert ("Success!")
} else {
alert ('failed')
}
You are probably referring to above alert that you didn't immediately see "Success". That doesn't happen like that, just like with updating state, when you dispatch something, you will see the update on the next render.
This useEffect may work, but you're kind of abusing the dependency array here. You're not actually depending on credentials.isEmailValid or credentials.isPasswordValid. You should use these dependencies to decide which action to dispatch, and maybe that's your plan already.
The reason your handleSubmit doesn't seem to work, is what others point out. You won't be able to see the result until next render, so not inside the handleSubmit function.
// useEffect(() => {
// dispatch({type: 'isCredValid'})
// }, [credentials.isEmailValid, credentials.isPasswordValid])
const handleSubmit = (e) => {
e.preventDefault()
dispatch({ type: "isCredValid" })
if (credentials.isCredValid === true) {
alert ("Success!")
} else {
alert ('failed')
}
}
To see the results, add another useEffect and trigger the alert from there:
useEffect(() => {
if(credentials.isCredValid){
alert('Success!')
}
}, [credentials])
I am using the following method to try to change the value of task.isComplete to !task.isComplete onClick.
handleComplete = (event) => {
event.preventDefault();
this.setState(
(prevState) => ({
listOfTasks: prevState.listOfTasks.map((task) => {
if (task.id === event.target.id) {
task.isComplete = !task.isComplete;
console.log(task);
}
return task;
}),
}),
() => console.log(this.state.listOfTasks)
);
};
when clicking the button the 2 logs are:
{nameOfTask: "aaaa", isComplete: true, id: "1610746018062"}
TodoListTest.js:54
[{…}]
0: {nameOfTask: "aaaa", isComplete: false, id: "1610746018062"}
length: 1
__proto__: Array(0)
React seems to only consider the second log state so I don't get the expected change, incomplete turning to true at the end of the operation.
you need to make a shallow copy of the task object to update its isComplete property.
handleComplete = (event) => {
event.preventDefault();
this.setState(
(prevState) => ({
listOfTasks: prevState.listOfTasks.map((task) => {
if (task.id === event.target.id) {
return {
...task,
isComplete: !task.isComplete
};
}
return task;
})
}),
() => console.log(this.state.listOfTasks)
);
};
I have the following onChange function
onChange = e => {
this.setState({
autocompleteValues: [],
showPredictivo: true
})
if (e.target.value.length > 3) this.partialSearch(e.target.value) //this is the one im concerned about
this.setState({ value: e.target.value, hasError: false, errorMsg: this.state.errorMsgSafe });
if (this.props.validateOn && (this.props.validateOn === undefined || this.props.validateOn === "onChange")) {
this.validaciones();
};
};
That function partialSearch will only trigger when the value length is more than 3, but i want to add another condition, and its to trigger it only if the value hasnt changed in the past 300ms.
How would i do that there?
EDIT: I have tried to add a debounce / throttle but it doesnt seem to work at all, the function is never triggered
This is my whole code
async partialSearch(searchTerm?: string) {
const environment = process.env.REACT_APP_ENV ? process.env.REACT_APP_ENV : "pre";
const api = process.env.REACT_APP_API ? process.env.REACT_APP_API : "my_api";
const api_version = "v2";
let baseUrl = getBaseUrl(api, environment) + "/search?q=" + searchTerm
return fetchSPA(baseUrl, undefined, { version: api_version })
.then(res => {
if (res && res.data && res.data.length) {
const firstFive = res.data[0].resultSearch?.cars.slice(0, 5).map(val => {
return { denominacion: val.denominacion, plate: val.id };
})
if (firstFive !== undefined) this.setState({ autocompleteValues: firstFive })
}
}).catch((res) => {
console.log("Error Fetch:: ", res)
});
}
onChange = e => {
this.setState({
autocompleteValues: [],
showPredictivo: true
})
if (e.target.value.length > 3) _.debounce(() => this.partialSearch(e.target.value), 300)
this.setState({ value: e.target.value, hasError: false, errorMsg: this.state.errorMsgSafe });
if (this.props.validateOn && (this.props.validateOn === undefined || this.props.validateOn === "onChange")) {
this.validaciones();
};
};
The component where its called
Without the debounce/throttle it works, but when adding it, its never triggered
<SCInput
type={type || "text"}
id={id ? id : null}
name={name}
disabled={disabled}
readOnly={readOnly}
style={style}
hasError={errorMsg ? true : false}
compact={compact}
onChange={onChange}
onBlur={onBlur}
placeholder={placeholder}
value={this.state.value}
>
You're most likely looking for a debounce / throttle here.
This article explains the scenario: https://css-tricks.com/debouncing-throttling-explained-examples/.
In short, by debouncing, you're wrapping a function over the function which gets called often. The "wrapper"-function is making sure, that the function inside only gets called again after a certain time has passed.
An easy way to implement this is to use a utility-library like lodash or you could google for a fitting debounce-function.
I want to update multiple state when I check the box mean the useSameInfo is true.
For example if fieldName is assignFirstName then I want to update billFirstName with this value also.
updateValue = (fieldName, event) => {
event.preventDefault();
if (this.state.useSameInfo) {
if (fieldName === 'assignFirstName') {
// update billFirstName state also
} else if (fieldName === 'assignLastName') {
// update billLastName state also
}
}
this.setState({
[fieldName]: event.target.value,
});
console.log(this.state);
};
What should I write in the line commented.
You could use an object and populate it accordingly before calling the setState
updateValue = (fieldName, event) => {
event.preventDefault();
const updates = {
[fieldName]: event.target.value
};
if (this.state.useSameInfo) {
if (fieldName === 'assignFirstName') {
updates.billFirstName = event.target.value;
} else if (fieldName === 'assignLastName') {
updates.billLastName = event.target.value;
}
}
this.setState(updates);
console.log(this.state);
};
I can't figure out why my input is not updating. Here is my code:
state = {
org: {
orgName: ''
}
};
updateInput = field => event => {
this.setState({
[field]: event.target.value
})
}
render() {
let { org } = this.state
return (
<input
value={org.orgName}
onChange={this.updateInput('orgName')}
/>
)
}
I type data into the input. It calls updateInput and sets the state. When render is called, the org.orgNameis '' again. This should be working.
I have even added a log in the setState callback:
this.setState({
[field]: event.target.value
}, () => console.log(this.state.org))
and it logs out the org info that has been entered into the input
What am I missing? How do I make this work?
You have a nested object in your state - you are updating this.state.orgName instead of this.state.org.orgName
updateInput = field => event => {
this.setState({
[field]: event.target.value
})
}
needs to be
updateInput = field => event => {
this.setState({
org: {
...this.state.org,
[field]: event.target.value
}
})
}
Would recommend you avoid nesting objects in state though going forward. Will prove difficult to optimize later on.