Important Hooks used in React - Pt.2

» Quick OverView

  1. Hey there, everyone! In the previous section, we learnt how to use useState, useEffect, and useContext hooks in react.

  2. In this section we will learn about the useRef hook, useCallback, and useMemo hooks as well as how to use them effectively. Let’s get into it!"

» useRef Hook

  1. useRef is used to persist values between renders without causing re-renders, making it perfect for tasks that involve DOM manipulation, storing mutable values, or if you want to track previous values.

  2. Basically, It can be used to store a mutable value that does not cause a re-render when updated.

  3. useRef returns an object called a ref object. This object has a single property called current.

useRef Slide

  1. Initially, current is set to the value that have passed as the argument to useRef.

  2. This current property can be changed to hold different values and will continue between renders of your component.

  3. Changing a ref does not trigger a re-render as it does in state variables.

» Example of useRef Hook:

  1. To use the useRef hook in react, we have to first import it from react.

  2. Then we will create and export a functional component called ‘MyComponent’.

  3. Inside this component, we declare a variable called ‘myRef’ using the useRef hook and initialize it with the value 0.

  4. Next, we will create a function called ‘handleClick’. This function will trigger when a button is clicked. Inside the ‘handleClick’ function, we will be accessing the current value of our ‘myRef’ variable, myRef is essentially a reference to the DOM input element that we will create.

  5. In the return statement. We’re rendering a heading ‘useRef Example’, and we also have an input field and a button. The input field here has a ‘ref’ attribute set to our ‘myRef’ variable, allowing us to directly reference this input field in our JavaScript code.

  6. And now save the file and on browser we have the heading, an input field, and a button.

  7. So, When the button is clicked, an alert will pop up, displaying the input value currently stored in our ‘myRef’ variable.

// App.js file
import React, { useRef } from 'react';

export default function MyComponent() {
  const myRef = useRef(0);

  const handleClick = () => {
    alert(`Input value: ${myRef.current.value}`);
  };

  return (
    <div>
      <h1>useRef Example</h1>
      <input ref={myRef} type="text" placeholder="Type something..." />
      <button onClick={handleClick}>Show Input Value</button>
    </div>
  );
}
  1. Below is how the output is: useRef Slide

» useMemo Hook

  1. The useMemo hook is used to return a memoized value.

  2. useMemo hook is used for memoization.

  3. Memoization means caching a value so that it does not need to be recalculated. It’s done to improve the performance by memoizing the result of a function call and returning the cached result when the inputs (dependencies) have not changed.

  4. useMemo takes two arguments: a function and an array of dependencies.

  5. In below example, expensive calulation is the function whose result will be memoized. It’s the function that will only be re-executed if one of the dependencies changes. In this case we just have only one dependcy that is count if it changes then function will be re-executed.

useRef Slide

» Example of useMemo Hook:

  1. First, we will use the below example just to show you the difference between what happens when we don’t use the useMemo hook and when we use the useMemo hook.

  2. We first import the react, and useState hook from React at the top of the component. And a CSS file for styling.

  3. Then we have a functional component named App.

  4. Inside the ‘App’ component, here we have two state variables: ‘count’ and ‘todos’. The ‘count’ state is initialized to 0, and ‘todos’ is an empty array. The ‘useState’ hook manages these states.

  5. We create a new constant variable named calculation. It will calculate its value by invoking the expensiveCalculation function defined below with the current value of the count variable passed as an argument.

  6. Then we have two functions ‘increment’ and ‘addTodo’.

  7. ‘increment’ will help us update the ‘count’ state by incrementing its current value by 1. And ‘addTodo’, on the other hand, will add a new item ‘New Todo’ to the ‘todos’ array using the spread operator.

  8. In the return statement, we display a list of todos that will come from the todos state. It maps through the ‘todos’ array and displays each to-do item as a paragraph element.

  9. The key attribute is used for React to efficiently update the UI. It will ensure that React can identify which items have changed, been added, or been removed.

  10. The ‘Add Todo’ button, which, when clicked, will trigger the ‘addTodo’ function that we have created above and will add a new item to the todos array.

  11. Count: {count} will displays the value of the count state variable. We use Curly braces {} to insert JavaScript variables or expressions into JSX.

  12. The button, when clicked, triggers the increment function. It will update the count state variable by incrementing its current value by 1.

  13. {calculation} will display the result of the calculation variable, which we calculate using the expensive calculation function with the current count value.

  14. Down, we have created an expensive calculation arrow function that takes a num as a parameter.

  15. The for loop here runs a billion times. Inside the loop, the num parameter is incremented by 1 during each iteration. This loop is the computationally expensive part of the function. We are just using it here to increase the processing power and time. It doesn’t have any practical purpose.

  16. After the loop completes, the function returns the final value of num

  17. When we try to use these buttons; you will see there’s a delay in addition to a new to-do and even when increasing the counter it takes a lot of time because it is re-rendering every time we click on these buttons.

  18. The expensive calculation function runs on every render.

  19. But this expensive calculation is just for the counter component right? Then why does it affect the To-do list?

  20. When the expensive calculation function is called, it doesn’t just impact the calculation variable; it also causes our entire component to re-render. React components re-render when their state or props change. In this case, whenever the count state variable changes, the count will trigger a re-render of the component. During this re-render, the todos state is also being processed, and if the component is unresponsive due to the intensive calculation, the rendering of todos gets delayed.

// App.js file
import { useState, useMemo } from "react";
import "./App.css";

export default function App() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);
  const calculation = expensiveCalculation(count);

  const increment = () => {
    setCount((c) => c + 1);
  };
  const addTodo = () => {
    setTodos((t) => [...t, "New Todo"]);
  };

  return (
    <div className="App">
      <div>
        <h2>To os</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>;
        })}
        <button onClick={addTodo}>Add Todo</button>
      </div>
      <hr />
      <div>
        <h2>Expensive Calculation</h2>
        Count: {count}
        <button onClick={increment}>+</button>
        <p>{calculation}</p>
      </div>
    </div>
  );
};

const expensiveCalculation = (num) => {
  console.log("Calculating...");
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
};
  1. Now to fix these kinds of issues we use the useMemo hook.

  2. Let’s import useMemo hook with the useState hook and then wrap the expensive calculation in a useMemo arrow function and call it with the current value of the count variable.

  3. The second argument to useMemo is an array of dependencies. In this case, it’s [count]. This means that the memoized value (calculation) will only be re-calculated when the value of the count variable changes.

  4. It will ensure that the expensive calculation is not re-executed unnecessarily, thus optimizing the performance of the component.

  5. Now save the file and reload the site and then when we click click add todo, it adds a new todo really fast and it doesn’t get affected by the expensive calculation. But when you click on the plus button, it will definitely take time to update the count because the count variable is updating and that will cause the component to re-render.

  6. So that’s the useMemo hook.

// App.js file
// import useMemo hook
import { useState, useMemo } from "react";
import "./App.css";

export default function App() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  // Changed
  const calculation = useMemo(() => expensiveCalculation(count), [count]);

  // Remaining code...
};

const expensiveCalculation = (num) => {
  console.log("Calculating...");
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
};

useRef Slide

» useCallback Hook

  1. The useCallback Hook in react returns a memoized callback function.

  2. The useCallback Hook will only run when one of its dependencies changes.

  3. useCallback and useMemo Hooks are similar.

  4. The main difference is that useMemo is used when you want to return a memoized value and useCallback is used when you want to return a memoized function.

  5. But both these hooks are used when we want to prevent a component to re-render unnecessarily.

useRef Slide

» Example of useCallback Hook:

  1. First off, this component is importing React and the ‘useState’ hook from React. Plus, it imports two custom components, ‘Button’ and ‘Count’ (will see this below.)

  2. Inside ‘ParentComponent.js’ file, we have two states: ‘increaseCount’ and ‘decreaseCount’. These states are managed by the ‘useState’ hook, initializing both counts to zero.

import React, { useState } from 'react';
import Button from './Button';
import Count from './Count';

export default function ParentComponent() {

    const [increaseCount, setincreaseCount] = useState(0);
    const [decreaseCount, setdecreaseCount] = useState(0);

    const incrementCount = () => {
        setincreaseCount(increaseCount + 1);
    }
    const decrementCount = () => {
        setdecreaseCount(decreaseCount - 1);
    }

    return (
        <div>
            <Count text="Current count is" count={increaseCount} />
            <Button handleClick={incrementCount}>Increase the count</Button>
            <Count text="Current count is" count={decreaseCount} />
            <Button handleClick={decrementCount}>Decrease the count</Button>
        </div>
    );
}
  1. Then we have an ‘incrementCount’ function which, when triggered, will update the ‘increaseCount’ state by adding 1. Similarly, the ‘decrementCount’ function will decrease the ‘decreaseCount’ state by 1."

  2. And then ‘ParentComponent is rendering two instances of the ‘Count’ component. The ‘Count’ component takes two props: ‘text’, which defines the description, and ‘count’, which takes the value of either ‘increaseCount’ or ‘decreaseCount’."

  3. Right after each ‘Count’ component, we have a ‘Button’ component. These ‘Button’ components will help in increasing or decreasing the count with the help of ‘incrementCount’ and ‘decrementCount’ functions. When we click them, the count state will be updated.

  4. Now let’s see what we have in count.js file:

import React from 'react';
function Count(props) {
    console.log("Count rendering");
    return (
        <div>
            {props.text} is {props.count}
        </div>
    );
}
export default Count;
  1. Here we have a functional component called Count that takes props as its argument. props are used to render UI elements. The props parameter contains any properties that are passed to this component.

  2. Then in the return statement, we return whatever value is passed to the text prop in our parent component. Similarly with count prop.

  3. And then in the Button.js file which is yet another part of our component, we have a functional component called Button that takes props as its argument.

import React from 'react';
function Button(props) {
    console.log(`Button clicked ${props.children}`);
    return (
        <div>
            <button onClick={props.handleClick}> {props.children} </button>
        </div>
    );
}
export default Button;
  1. Then we return a button element. The onClick attribute of the button is set to the function passed via the handleClick prop. The content inside the button props.children are also dynamic and will represent whatever content will be placed between the tags of the Button component.

  2. And finally, in the App.js file, we call our parent component.

import ParentComponent from "./components/ParentComponent";
import './App.css';
function App() {
  return (
    <div className="App">
      <ParentComponent />
    </div>
  );
}
export default App;
  1. Now when you open the console you will have these statements that show both the button and count have rendered initially as they should. But When you click on the increase the count button, you will find not only this click re-rendered the increase button and count but it also re-rendered the decrease button and count. We don’t want this to happen.

  2. So that’s when we will use a callback hook.

  3. Head back to the code and what will do is first wrap the button and count the component with React.memo just like this.

export default React.memo(Button); // In Button.js file

export default React.memo(Count); // In count.js file
  1. What this will do is: prevent these functional components from being re-rendered if their props or state have not changed.

  2. Then just import the use callback hook and update the increment Count and decrement count to a useCallback arrow function preventing unnecessary re-creations.

// Import callback hook
import React, { useState, useCallback } from 'react';
import Button from './Button';
import Count from './Count';

export default function ParentComponent() {

    const [increaseCount, setincreaseCount] = useState(0);
    const [decreaseCount, setdecreaseCount] = useState(0);

    // Changed
    const incrementCount = useCallback(() => {
        setincreaseCount(increaseCount + 1);
    }, [increaseCount]);

    // Changed
    const decrementCount = useCallback(() => {
        setdecreaseCount(decreaseCount - 1);
    }, [decreaseCount]);


    // Remaining code...
}
  1. Here the dependency arrays [increaseCount] and [decreaseCount] are used to specify when the memoized functions should be re-created.

  2. Now when we click on the increase the count button; it will only recreate the incrementCount function. Same with decreasing the count; it won’t re-create the incrementCount function.

  3. Below is the example:

useRef Slide

  1. You can also refer to react.dev documentation for this.

And That’s it! You’ve successfully learned about ‘useRef’, ‘useMemo’, and ‘useCallback’ hooks in REACT.

Thank You for reading and Happy coding!😊