React Tutorial – Learn React in 5 hours – Part 4

React functional component
Share

In the previous section, we will discuss react functional components and some hook concepts like useState, useEffect and useContext. Now we discuss remaining hook concepts. 

React useRef Hook

Use the useRef Hook to persist values between renders.

You can use it to store mutable values that don’t require re-rendering when they are updated.

You can use it to access DOM elements directly.

Does Not Cause Re-renders

If we tried to count the number of times the useState Hook renders our application, we would be caught in an infinite loop as this Hook itself causes a re-render.

To avoid this, we can use the useRef Hook.

Example:
Use useRef to track application renders.

import { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";

function App() {
  const [inputValue, setInputValue] = useState("");
  const count = useRef(0);
  useEffect(() => {
    count.current = count.current + 1;
  });

  return (
    <>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h1>Render Count: {count.current}</h1>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
Output:
React useRef Hook

The useRef() method only returns one item. It returns an Object called current.

Initializing useRef, we set its initial value.(useRef(0)).

It's like doing this: const count = {current: 0}. 
Use count.current to get the current count.

Try typing in the input on your computer to see the render count increase.

Accessing DOM Elements

Generally, we want React to handle all DOM manipulation.

However, in some cases useRef can be used without causing issues.

We can add a ref attribute to an element in React to directly access it in the DOM.

Example:
Use useRef to focus the input:

import { useRef } from "react";
import ReactDOM from "react-dom";

function App() {
  const inputElement = useRef();
  const focusInput = () => {
    inputElement.current.focus();
  };

  return (
    <>
      <input type="text" ref={inputElement} />
      <button onClick={focusInput}>Focus Input</button>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
Output:
React useRef Hook Accessing DOM

Tracking State Changes

In addition, the useRef Hook can be used to keep track of previous state values.

This is because we are able to persist useRef values across renders.

Example:
Use useRef to keep track of previous state values:

import { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";

function App() {
  const [inputValue, setInputValue] = useState("");
  const previousInputValue = useRef("");
  useEffect(() => {
    previousInputValue.current = inputValue;
  }, [inputValue]);

  return (
    <>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h2>Current Value: {inputValue}</h2>
      <h2>Previous Value: {previousInputValue.current}</h2>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
Output:
React useRef Hook Tracking state Change

To keep track of the previous state, we use a combination of useState, useEffect, and useRef.

We update the useRef current value each time the inputValue is updated by entering text into the input field within the useEffect.

React useMemo Hook

The React useMemo Hook returns a memoized value.

Memories cache values so that they do not have to be recalculated.

The useMemo Hook runs only when a dependency updates.

This can improve performance.

The useMemo and useCallback Hooks are similar. UseMemo returns a memoized value, while useCallback returns a memoized function.

Performance

By using the useMemo Hook, you can prevent expensive, resource-intensive functions from running needlessly.

We have an expensive function that runs on every render in this example.

If you modify the count or add to everything, you will notice a delay in execution.

Example:
It performs poorly. The expensiveCalculation function runs on every render:

import { useState } from "react";
import ReactDOM from "react-dom";

const 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>
      <div>
        <h2>My Todos</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>;
        })}
        <button onClick={addTodo}>Add Todo</button>
      </div>
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
        <h2>Expensive Calculation</h2>
        {calculation}
      </div>
    </div>
  );
};

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

ReactDOM.render(<App />, document.getElementById('root'));
Output:
React useMemo Hook Performance

Use useMemo

We can fix this performance issue by using the useMemo Hook to memoize the expensiveCalculation function. In this manner, the expensiveCalculation function only runs when necessary.

With useMemo, we can wrap the expensive function call.

A second parameter to useMemoHook declares dependencies. The expensive function will only run when its dependencies have changed.

As seen in the following example, the expensive function is only executed when the count is changed and not when tasks are added.

Example:
Performance example using the useMemo Hook:

import { useState, useMemo } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);
  const calculation = useMemo(() => expensiveCalculation(count), [count]);

  const increment = () => {
    setCount((c) => c + 1);
  };

  const addTodo = () => {
    setTodos((t) => [...t, "New Todo"]);
  };

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

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

ReactDOM.render(<App />, document.getElementById('root'));
Output:
React useMemo Hook

React useCallback Hook

The useCallback Hook for React returns a memoized callback function.

By doing this, we can isolate resource-intensive functions so that they don’t run automatically on every render.

Whenever one of its dependencies updates, the useCallback Hook runs.

It can improve performance.

Problem

The purpose of useCallback is to prevent re-rendering of a component unless its properties have changed.

As an example, you might think that the Todos component will not re-render unless the todos change:

This is a similar example to the one in the userMmeo section.

Example:
index.js

import { useState } from "react";
import ReactDOM from "react-dom";
import Todos from "./Todos";

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

  const increment = () => {
    setCount((c) => c + 1);
  };

  const addTodo = () => {
    setTodos((t) => [...t, "New Todo"]);
  };

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
Todos.js

import { memo } from "react";

const Todos = ({ todos, addTodo }) => {
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
      <button onClick={addTodo}>Add Todo</button>
    </>
  );
};

export default memo(Todos);
Output:
React useCallBack Hook Problem

Run this and click the count increment button.

It is noticeable that the Todos component re-renders even when the tasks don’t change.

When we increase the todo count, neither the todos state nor the addTodo function should re-render since we are using memo.

The reason is a concept known as “referential equality”.

Re-rendering a component recreates its functions, so adding todo actually changed.

Solution

Use the useCallback hook to prevent the function from being recreated unless absolutely necessary.

By using the useCallback Hook, the Todos component will not be re-rendered excessively:

Example:
index.js

import { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import Todos from "./Todos";

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

  const increment = () => {
    setCount((c) => c + 1);
  };

  const addTodo = useCallback(() => {
    setTodos((t) => [...t, "New Todo"]);
  }, [todos]);

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
Todos.js

import { memo } from "react";

const Todos = ({ todos, addTodo }) => {
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
      <button onClick={addTodo}>Add Todo</button>
    </>
  );
};

export default memo(Todos);
Output:
React useCallBack Hook Solution

The Todos component will now only re-render when the Todos prop changes.

Share

Leave a Comment

Your email address will not be published. Required fields are marked *

Share