Performance efficient way to use conditionals inside useEffect

Last updated : Jul 30, 2023 12:00 AM

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;
Figure 1: Unnecessary re-renders in useEffect
Figure 1: Unnecessary re-renders in useEffect

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
}
Figure 2: Minimize re-renders in useEffect
Figure 2: Minimize re-renders in useEffect
Lance

By: Lance

Hi, I'm Lance Raney, a dedicated Fullstack Developer based in Oklahoma with over 15 years of exp

Read more...