When you use conditionals inside the useEffect
hook, it is good practice to compare the state variables with their previous values. Let's see why.
Take a look at the below code. I am limiting input to digits only. When I type a number, the else
block will execute and set the
isValid
state to true
and the isValid
state to false for any character inputs.
That happens for every single keyboard input. That is inefficient and unnecessary.
import { useEffect, useRef, useState } from "react";
const App = () => {
const [isValid,setIsValid] = useState(true)
const [inputValue,setInputValue] = useState("")
const prevInputValue = usePrevious(inputValue)
useEffect(() => {
if(inputValue.match(/[a-z]/i)){
setIsValid(false)
//This block executes for every alpha input
console.log("isValid set to false")
}
else {
setIsValid(true)
//This block executes for every numeric input
console.log("isValid set to true")
}
},[inputValue, isValid])
return (
<div>
<input type="text" onChange={(e) => setInputValue(e.target.value)}/><br/>
{!isValid && <>Inalid<br/></>}
<button disabled={!isValid}>Submit</button>
</div>
);
}
export default App;

I must pay attention to the variables used in the if and else conditions to fix that. If the inputValue
is invalid, the isValid
state is
set to false
. I don't have to set it to false
for every invalid input. Instead, I must check the previous validity of the inputValue
.
If the previous value of the inputValue
is valid and the current value is not, then only I set the isValid
to false.
The same rule applies to every conditional block.
if(previousInputValid === true && currentInputValid === false){
setIsValid(false)
}
else
if(previousInputValid === false && currentInputValid === true){
setIsValid(true)
}
Here is the complete code example to illustrate that. I have created a custom hook to obtain the previous value. You can read more about it in Accessing the previous state in React Js
import { useEffect, useRef, useState } from "react";
const App = () => {
const [isValid,setIsValid] = useState(true)
const [inputValue,setInputValue] = useState("")
const prevInputValue = usePrevious(inputValue)
useEffect(() => {
if(!prevInputValue?.match(/[a-z]/i) && inputValue.match(/[a-z]/i)){
setIsValid(false)
console.log("isValid set to false")
}
else
if(prevInputValue?.match(/[a-z]/i) && !inputValue.match(/[a-z]/i)){
setIsValid(true)
console.log("isValid set to true")
}
},[inputValue, isValid, prevInputValue])
return (
<div>
<input type="text" onChange={(e) => setInputValue(e.target.value)}/><br/>
{!isValid && <>Inalid<br/></>}
<button disabled={!isValid}>Submit</button>
</div>
);
}
export default App;
const usePrevious = (value: string) => {
const previousValue = useRef<string>()
useEffect(() => {
previousValue.current = value
}, [value])
return previousValue.current
}
