is one of the most widely used hooks in React. Optimizing in React gives you a significant boost in performance and sometimes gets your code rid of nasty bugs. I've encountered many situations where there were performance issues or some difficult-to-find bugs due to incorrect usage of . Here I'll discuss a few of the optimization patterns which are very easy to adhere to yet very effective. useEffect useEffect useEffect 1. Use primitives in the dependency array We often pass JavaScript objects in the dependency array. Although nothing is wrong with doing that, sometimes it makes more sense to destructure the JavaScript object and only pass the specific value to the dependency array of . Let's take a look at an example. Suppose we have a component which takes the object as a prop and fetches the user's transaction data whenever the user changes. We need to get the user's transaction data when the user changes. Our user object looks like this: useEffect useEffect UserInfo user only { "id": 123, "name": "John Doe", "imageURL": "...", "isActive": false } The typical implementation will look like this: function UserInfo({ user }) { useEffect(() => { getUserData(user.id); }, [user]); return ( ... ); } This will get user data from the server whenever the user object changes. But looking at the user object, it only makes sense to get the new data when the changes. There could be instances where the user's data changes but the don't, which won't benefit us in making an API call and getting the data from the server again. Let's say the user's was initially and later changed to . In this case, we don't need to get the user's transaction data again because the user is still the same. We can avoid this redundant API call by changing our dependencies. Here's the refactored component using primitive data types: user.id id isActive false true useEffect function UserInfo({ user }) { const { id } = user; useEffect(() => { getUserData(id); }, [id]); return ( ... ); } With the above optimization of , we will only get the user's transactional data when the user changes. Remember, in dependencies, non-primitives (like objects and arrays) are checked for equality only by reference, not by the value. So the following code will always return : useEffect id useEffect false { name: "Huzaima" } === { name: "Huzaima" } 2. Avoid needless dependencies Sometimes, we use to derive a new state from our current state. Suppose we've a counter which totals the length of the string every time the input value is updated. We can implement that like this: useEffect function CharacterCounter() { const [inputValue, setInputValue] = useState(""); const [compoundStringLength, setCompoundStringLength] = useState(0); useEffect(() => { if (inputValue) { setCompoundStringLength(compoundStringLength + inputValue.length); } }, [inputValue, compoundStringLength]); return ( ... ); } The above code results in being executed infinitely. Why? Because of the incorrect dependency. We're updating inside the and using the same as a dependency. How do we solve this problem? We need to use inside the for computation, so we can't get rid of it. But what we can do is remove it from the dependency array. You might think this will result in an incorrect value of due to being a closure. You're right... and wrong. This is correct because is a closure, so we won't get the correct value of if we don't use it in the dependency array. Still, there's a way of getting the correct value of without specifying it in the dependency array. The state setter function ( ) doesn't only accept value but also a function. The signature of that function looks like this: useEffect compoundStringLength useEffect compoundStringLength useEffect compoundStringLength useEffect useEffect compoundStringLength compoundStringLength setCompoundStringLength setCompoundStringLength: (currentStateValue) => newStateValue; We can leverage the above function signature in the to get the correct value of for our computations . The refactored optimization of looks like this: useEffect compoundStringLength without specifying it as a dependency useEffect function CharacterCounter() { const [inputValue, setInputValue] = useState(""); const [compoundStringLength, setCompoundStringLength] = useState(0); useEffect(() => { if (inputValue) { setCompoundStringLength( (compoundStringLength) => compoundStringLength + inputValue.length ); } }, [inputValue]); return ( ... ); } 3. Hoist functions Any unmemoized function that you define inside the component is recreated on every render. A very naive example is this: function EmailValidate() { const [email, setEmail] = useState(""); const isEmailValid = (input) => { const 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(input)) return "Email is valid"; return "Email is invalid"; }; return ( <> <input value={email} onChange={(event) => setEmail(event.target.value)} /> <p>{isEmailValid(email)}</p> </> ); } Here you can see that the function is a pure function and doesn't depend on the component's props and/or state. We can easily optimize this by extracting the function outside the component. Rewriting the above component will look like this: isEmailValid const isEmailValid = (input) => { const 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(input)) return "Email is valid"; return "Email is invalid"; }; function EmailValidate() { const [email, setEmail] = useState(""); return ( <> <input value={email} onChange={(event) => setEmail(event.target.value)} /> <p>{isEmailValid(email)}</p> </> ); } 4. Clean up The primary purpose of is to handle side effects. Sometimes you have to clean up those side effects like event handler subscriptions or observables. You can return the function having clean-up logic from the function inside . Let us take an example of an input field which should clear when the user presses the key. We can do so like this: useEffect useEffect Escape function Input() { const [input, setInput] = useState(""); const escFunction = useCallback((event) => { if (event.key === "Escape") { setInput(""); } }, []); useEffect(() => { document.addEventListener("keydown", escFunction, false); return () => { document.removeEventListener("keydown", escFunction, false); }; }, [escFunction]); return ( <> <input value={input} onChange={(event) => setInput(event.target.value)} /> </> ); } In the above code, we're returning a function from inside the . That function unsubscribes the event listener, saving us from nasty memory leak problems. useEffect keydown 5. Use multiple effects to separate concerns Ever heard of divide and conquer? Yup! That's precisely what we need to do here. Aka , a widely practiced principle in software engineering. in , i.e. using different for every use case. Suppose we've a component which gets and in props. We make an API call to get user and organization data based on the props. We can write like this: separation of concerns React team advocates for separation of concerns useEffect useEffect userId organizationId useEffect useEffect(() => { getUser(userId); getOrganization(organizationId); }, [userId, organizationId]); The above will work fine. But there's one fundamental problem. If the change but the don't, will still get called. Why? Because we've added two separate concerns in a single . We need to segregate them into two different . We can rewrite it like this: useEffect userId organizationId getOrganization useEffect useEffect useEffect(() => { getUser(userId); }, [userId]); useEffect(() => { getOrganization(organizationId); }, [organizationId]); 6. Custom hooks Custom hooks are a great way of extracting side effects, which can be reused by many other components. It's also an elegant way of organizing your code. Suppose we've a custom hook to return details about the input string. We can write that custom hook like this: function useStringDescription(inputValue) { if (!inputValue) { return defaultValue; } else { const wordCount = inputValue.trim().split(/\s+/).length; const noOfVowels = inputValue.match(/[aeiou]/gi)?.length || 0; return { wordCount, noOfVowels, }; } } The hook returns the number of vowels and the word count of a string passed in the argument. We can use this hook like this: useStringDescription function App() { const [inputValue, setInputValue] = useState(""); const stringDescription = useStringDescription(inputValue); const { wordCount, noOfVowels } = stringDescription; useEffect(() => { console.log("stringDescription changed", stringDescription); }, [stringDescription]); return ( ... ); } Let's try to use the above setup and see how many times the is executed. useEffect You can see that is called even when the object's value doesn't change. We can destructure the object as defined in the 1st rule of this article, or we can optimize our custom hook like this: useEffect stringDescription stringDescription function useStringDescription(inputValue) { const [stringDescription, setStringDescription] = useState(defaultValue); useEffect(() => { if (!inputValue) { setStringDescription(defaultValue); } else { const wordCount = inputValue.trim().split(/\s+/).length; const noOfVowels = inputValue.match(/[aeiou]/gi)?.length || 0; if ( wordCount !== stringDescription.wordCount || noOfVowels !== stringDescription.noOfVowels ) { setStringDescription({ wordCount, noOfVowels, }); } } }, [inputValue, stringDescription.noOfVowels, stringDescription.wordCount]); return stringDescription; } Now, we're updating the state when any value changes. Let's check out the results: See? didn't get called needlessly because the referential equality holds. useEffect These were the bunch of optimizations we can use to make the usage of hooks more effective and get rid of nasty and notoriously hard to find bugs caused due to incorrect usage of hooks. Optimizing in React also gives you a boost in performance. What are your secret recipes? Let me know on . And don't forget to read my previous blog about . useEffect useEffect useEffect Twitter promise speed up Also published here.