Week 6.2 useEffect, useMemo, useCallback
In this lecture, Harkirat explores key aspects of React, starting with React Hooks like useEffect, useMemo, useCallback, and more, providing practical insights into state management and component functionalities. The discussions extend to creating custom hooks for reusable logic. Prop drilling, a common challenge in passing data between components, is addressed, offering effective solutions. The lecture also covers the Context API, a powerful tool for simplified state management across an entire React application.
Side Effects in React
In the context of React, side effects refer to operations or behaviors that occur outside the scope of the typical component rendering process. These can include data fetching, subscriptions, manual DOM manipulations, and other actions that have an impact beyond rendering the user interface.
Thus, “side effects” are the operations outside the usual rendering process, and “hooks,” like useEffect, are mechanisms provided by React to handle these side effects in functional components. The useEffect hook allows you to incorporate side effects into your components in a clean and organized manner.
React Hooks
React Hooks are functions that allow functional components in React to have state and lifecycle features that were previously available only in class components. Hooks were introduced in React 16.8 to enable developers to use state and other React features without writing a class.
Using these hooks, developers can manage state, handle side effects, optimize performance, and create more reusable and readable functional components in React applications. Each hook serves a specific purpose, contributing to a more modular and maintainable codebase.
Some commonly used React Hooks are:
1. useState()
useState is a React Hook that enables functional components to manage state. It returns an array with two elements: the current state value and a function to update that value.
Here’s an example of how to use useState:
import React, { useState } from 'react';
const Counter = () => { // Using useState to initialize count state with an initial value of 0 const [count, setCount] = useState(0);
// Event handler to increment count const increment = () => { // Using the setCount function to update the count state setCount(count + 1); };
return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> );};
export default Counter;In this example:
- We import the
useStatefunction from the ‘react’ package. - Inside the
Countercomponent, we useuseState(0)to initialize the state variablecountwith an initial value of 0. - The
countstate and thesetCountfunction are destructured from the array returned byuseState. - The
incrementfunction updates thecountstate by callingsetCount(count + 1)when the button is clicked. - The current value of the
countstate is displayed within a paragraph element.
The above example helps us understand how
useStatehelps manage and update state in functional components, providing a straightforward way to incorporate stateful behavior into React applications.
2. useEffect()
useEffect is a React Hook used for performing side effects in functional components. It is often used for tasks such as data fetching, subscriptions, or manually changing the DOM. The useEffect hook accepts two arguments: a function that contains the code to execute, and an optional array of dependencies that determines when the effect should run.
Here’s an example of how to use useEffect:
import React, { useState, useEffect } from 'react';
const DataFetcher = () => { const [data, setData] = useState(null);
useEffect(() => { // Effect will run after the component is mounted const fetchData = async () => { try { // Simulating a data fetching operation const response = await fetch('<https://api.example.com/data>'); const result = await response.json(); setData(result); } catch (error) { console.error('Error fetching data:', error); } };
fetchData();
// Effect cleanup (will run before unmounting) return () => { console.log('Component will unmount. Cleanup here.'); }; }, []); // Empty dependency array means the effect runs once after mount
return ( <div> {data ? ( <p>Data: {data}</p> ) : ( <p>Loading data...</p> )} </div> );};
export default DataFetcher;In this example:
- We import
useStateanduseEffectfrom ‘react’. - Inside the
DataFetchercomponent, we useuseStateto manage the state of thedatavariable. - The
useEffecthook is employed to perform the data fetching operation when the component is mounted. The empty dependency array[]ensures that the effect runs only once after the initial render. - The
fetchDatafunction, declared inside the effect, simulates an asynchronous data fetching operation. Upon success, it updates thedatastate. - The component returns content based on whether the data has been fetched.
useEffectis a powerful tool for managing side effects in React components, providing a clean way to handle asynchronous operations and component lifecycle events.
Problem Statement
3. useMemo()
useMemo is a React Hook that is used to memoize the result of a computation, preventing unnecessary recalculations when the component re-renders. It takes a function (referred to as the “memoized function”) and an array of dependencies. The memoized function will only be recomputed when the values in the dependencies array change.
Here’s an example of how to use useMemo:
import React, { useState, useMemo } from 'react';
const ExpensiveCalculation = ({ value }) => { const expensiveResult = useMemo(() => { // Simulating a computationally expensive operation console.log('Calculating expensive result...'); return value * 2; }, [value]); // Dependency array: recalculates when 'value' changes
return ( <div> <p>Value: {value}</p> <p>Expensive Result: {expensiveResult}</p> </div> );};
const MemoExample = () => { const [inputValue, setInputValue] = useState(5);
return ( <div> <input type="number" value={inputValue} onChange={(e) => setInputValue(Number(e.target.value))} /> <ExpensiveCalculation value={inputValue} /> </div> );};
export default MemoExample;In this example:
- We import
useStateanduseMemofrom ‘react’. - The
ExpensiveCalculationcomponent takes a propvalueand usesuseMemoto calculate an “expensive” result based on that value. - The dependency array
[value]indicates that the memoized function should be recomputed whenever thevalueprop changes. - The
MemoExamplecomponent renders aninputelement and theExpensiveCalculationcomponent. Thevalueprop ofExpensiveCalculationis set to the current state ofinputValue. - As you type in the input, you’ll notice that the expensive result is only recalculated when the input value changes, thanks to
useMemo.
useMemois particularly useful when dealing with expensive calculations or when you want to optimize performance by avoiding unnecessary computations during renders. It’s important to use it judiciously, as overusing memoization can lead to increased complexity.
4. useCallback()
useCallback is a React Hook that is used to memoize a callback function, preventing unnecessary re-creation of the callback on each render. This can be useful when passing callbacks to child components to ensure they don’t trigger unnecessary renders.
Here’s an example of how to use useCallback:
import React, { useState, useCallback } from 'react';
const ChildComponent = ({ onClick }) => { console.log('ChildComponent rendering...'); return <button onClick={onClick}>Click me</button>;};
const CallbackExample = () => { const [count, setCount] = useState(0);
// Regular callback function const handleClick = () => { console.log('Button clicked!'); setCount((prevCount) => prevCount + 1); };
// Memoized callback using useCallback const memoizedHandleClick = useCallback(handleClick, []);
return ( <div> <p>Count: {count}</p> <ChildComponent onClick={memoizedHandleClick} /> </div> );};
export default CallbackExample;In this example:
- We import
useStateanduseCallbackfrom ‘react’. - The
ChildComponentreceives a proponClickand renders a button with that click handler. - The
CallbackExamplecomponent maintains acountstate and has two callback functions:handleClickandmemoizedHandleClick. handleClickis a regular callback function that increments the count and logs a message.memoizedHandleClickis created usinguseCallback, and its dependency array ([]) indicates that it should only be re-created if the component mounts or unmounts.- The
ChildComponentreceives the memoized callback (memoizedHandleClick) as a prop. - As you click the button in the
ChildComponent, the count increases, and you’ll notice that the log statement insidehandleClickis only printed once, thanks touseCallbackpreventing unnecessary re-creations of the callback.
Using
useCallbackbecomes more crucial when dealing with complex components or components with frequent re-renders, optimizing performance by avoiding unnecessary function creations.
Difference between useEffect, useMemo & useCallback
-
useEffect:-
Purpose: Manages side effects in function components.
-
Triggers: Runs after rendering and on subsequent re-renders.
-
Use Cases: Fetching data, subscriptions, manually changing the DOM, etc.
-
Syntax:
useEffect(() => {// Side effect logic herereturn () => {// Cleanup logic (optional)};}, [dependencies]);
-
-
useMemo:-
Purpose: Memoizes the result of a computation to avoid unnecessary recalculations.
-
Triggers: Runs during rendering.
-
Use Cases: Memoizing expensive calculations, preventing unnecessary re-renders.
-
Syntax:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
-
-
useCallback:-
Purpose: Memoizes a callback function to prevent unnecessary re-renders of child components.
-
Triggers: Runs during rendering.
-
Use Cases: Preventing unnecessary re-renders when passing callbacks to child components.
-
Syntax:
const memoizedCallback = useCallback(() => {// Callback logic here}, [dependencies]);
-
In summary,
useEffectis for handling side effects,useMemois for memoizing values, anduseCallbackis for memoizing callback functions. Each serves a different purpose in optimizing and managing the behavior of React components.
Significance of Returning a Component from useEffect
In the provided code snippet, the we utilize the useEffect hook along with the setInterval function to toggle the state of the render variable every 5 seconds. This, in turn, controls the rendering of the MyComponent or an empty div based on the value of render. Let’s break down the significance of returning a component from useEffect:
import React, { useEffect, useState } from 'react';import './App.css';
function App() { const [render, setRender] = useState(true);
useEffect(() => { // Toggle the state every 5 seconds const intervalId = setInterval(() => { setRender(r => !r); }, 5000);
// Cleanup function: Clear the interval when the component is unmounted return () => { clearInterval(intervalId); }; }, []);
return ( <> {render ? <MyComponent /> : <div></div>} </> );}
function MyComponent() { useEffect(() => { console.error("Component mounted");
// Cleanup function: Log when the component is unmounted return () => { console.log("Component unmounted"); }; }, []);
return <div> From inside MyComponent </div>;}
export default App;Understanding the Code
- The
useEffecthook is used to create a side effect (in this case, toggling therenderstate at intervals) when the component mounts. - A cleanup function is returned within the
useEffect, which will be executed when the component is unmounted. In this example, it clears the interval previously set bysetInterval. - By toggling the
renderstate, the component (MyComponentor an emptydiv) is conditionally rendered or unrendered, demonstrating the dynamic nature of component rendering. - The
returnstatement within theuseEffectofMyComponentis used to specify what should be rendered when the component is active, in this case, a simpledivwith the text “From inside MyComponent.”
In summary, the ability to return a cleanup function from
useEffectis crucial for managing resources, subscriptions, or intervals created during the component’s lifecycle. It helps ensure proper cleanup when the component is no longer in use, preventing memory leaks or unintended behavior.