React Hooks: The Base of React Components [Part - 3]
This article explains useCallback, useMemo, useRef and useLayoutEffect hooks in detail.
What we have learned till now?
We have learned what react hooks are and how it is very useful.
We have learned the use and implementation of
useState
,useEffect
,useContext
anduseReducer
hooks.Previous Blog - Read Here
useCallback Hook
useCallback
is a React Hook that lets you cache a function definition between re-renders.
I repeat it caches function definition.
Why there is a need to cache the function?
In the context of React and JavaScript, the same function definition can refer to different instances of the function, which are not equal to each other.
-> This is because, in JavaScript, functions are treated as first-class citizens, and they can be assigned to variables, passed as arguments to other functions, or returned as values from functions.
function add(a, b) { return a + b; } const add1 = add; const add2 = add; const add3 = function(a, b) { return a + b; }; console.log(add1 === add2); // true console.log(add1 === add3); // false
In this example,
add1
andadd2
are both references to the sameadd
function definition butadd3
is a new function definition, even though it has the same code asadd1
.
Syntax of caching a function fn
using useCallback
const cachedFn = useCallback(fn, dependencies)
How useCallback optimizes the application?
Let's understand it with an example:
import React, { useState, useCallback } from "react";
const TodoItems = React.memo(
({ todoItems }) => {
return (
<ul>
{todoItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
},
(prevProps, nextProps) => {
return prevProps.todoItems === nextProps.todoItems;
}
);
const CahchedComponent = React.memo(({ cachedFunction }) => {
console.log("CachedComponent rendered!");
return <button onClick={cachedFunction}>CachedButton</button>;
});
const NonCahchedComponent = React.memo(({ nonCachedFunction }) => {
console.log("NonCachedComponent rendered!");
return <button onClick={nonCachedFunction}>NonCachedButton</button>;
});
function TodoList() {
const [todoItems, setTodoItems] = useState([]);
const [inputValue, setInputValue] = useState("");
const updateInputValue = useCallback((e) => {
setInputValue(e.target.value);
}, []);
const addTodoItem = () => {
const updatedTodoItems = [...todoItems, inputValue];
setTodoItems(updatedTodoItems);
setInputValue("");
};
const nonCachedFunction = () => {
console.log("Printing todoItems in non-cached function", todoItems);
};
const cachedFunction = useCallback(() => {
console.log("Printing todoItems in cached function", todoItems);
}, [todoItems]);
return (
<div>
<h1> Todo List </h1>
<TodoItems todoItems={todoItems} addTodoItem={addTodoItem} />
<input value={inputValue} onChange={updateInputValue} />
<button onClick={addTodoItem}> Add Item </button>
<div style={{ margin: 10 }}>
<CahchedComponent cachedFunction={cachedFunction} />
<NonCahchedComponent nonCachedFunction={nonCachedFunction} />
</div>
</div>
);
}
export default TodoList;
Explanation
React.memo
is used to optimize the performance of functional components. When a component is memoized, React will only re-render it if its props have changed. This can help avoid unnecessary re-renders and improve performance.The use of
useCallback
in this example is used to cache thecachedFunction
so that it only gets created once. This is useful because the definition of thecachedFunction
depends on thetodoItems
state, which doesn't change often. By caching it withuseCallback
, we ensure that the same function instance is used in subsequent renders, avoiding the need to re-create it each time.On the other hand, the nonCachedFunction is not cached with useCallback, so it will be re-created on every render. This leads to a change in its reference and NonCachedComponent that depends on it will also re-render. This can cause performance issues if the components are expensive to render.
The
addTodoItem
function is not memoized because it changes every time thetodoItems
orinputValue
state changes. Additionally, there is no component that depends on this function, so there is no need to cache it with useCallback.This means we should use
useCallback
when it's required not all the time because it may cause memory issues also.
useMemo Hook
useMemo
is a React Hook that lets you cache the result of a calculation between re-renders.
It is similar to useCallback
but instead of caching the function definition
it caches result of the function
and the rest of the things are same as useCallback
.
Syntax of writing a useMemo
Hook
const cachedValue = useMemo(calculateValue, dependencies)
Let's understand useMemo using an example:
import React, { useState, useMemo } from "react";
const slowFunction = (num, multiplier) => {
console.log("Calling Slow Function with multiplier", multiplier);
for (let i = 0; i < 1000000000; i++) {}
return num * multiplier;
};
export default function App() {
const [number, setNumber] = useState(0);
const [dark, setDark] = useState(false);
const doubleNumber = useMemo(() => slowFunction(number, 2), [number]);
const trippleNumber = slowFunction(number, 3);
const themeStyles1 = {
backgroundColor: dark ? "blue" : "white",
color: dark ? "white" : "blue"
};
const themeStyles2 = {
backgroundColor: dark ? "black" : "white",
color: dark ? "white" : "black"
};
return (
<div>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
/>
<button onClick={() => setDark((prevDark) => !prevDark)}>
Change Theme
</button>
<div style={themeStyles1}>Cached Double - {doubleNumber} </div>
<div style={themeStyles2}>Non cached tripple - {trippleNumber} </div>
</div>
);
}
Output
Explanation
In this example, we have two numbers, doubleNumber and trippleNumber, that are calculated using the
slowFunction
. TheslowFunction
takes some time to complete its calculation because it has an empty loop in it.The difference between doubleNumber and trippleNumber is that doubleNumber is calculated using the
useMemo
hook, which helps us optimize performance. TheuseMemo
hook remembers the result of the calculation and re-uses it when the component re-renders, instead of re-calculating the result.On the other hand, trippleNumber is not memoized, which means that every time the component re-renders, a new calculation is performed, leading to slower performance.
When the Change Theme button is clicked and the component re-renders, the doubleNumber remains the same, while the trippleNumber is recalculated, resulting in slower performance.
In simple terms, the
useMemo
hook helps us cache the results of expensive calculations, so we don't have to recalculate them every time the component re-renders. This helps us optimize the performance of our React applications.
useRef Hook
The useRef
hook is a way to access the value of a DOM element or a component instance in React. It is used to maintain a reference to a value across renders.
Unlike state, which is updated and causes a re-render when its value changes, the value stored in a ref does not cause a re-render. Instead, it remains unchanged across renders, making it ideal for storing values that should persist across renders, such as the value of a text input or a reference to a DOM element.
Here's an example of how to use the useRef
hook:
import React, { useRef } from 'react';
function Input() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
In this example, inputRef
is a reference to the text input DOM element. We set the ref
attribute of the input element to inputRef
, which allows us to access the input element in the handleClick
function. When the button is clicked, handleClick
is called and the focus
method is invoked on the input element, causing it to receive focus.
In conclusion, the useRef
hook is a way to access values that persist across renders in React, and is particularly useful for accessing DOM elements or component instances.
useLayoutEffect
useLayoutEffect
is a React hook that is used for running side effects after rendering. The difference between useLayoutEffect
and useEffect
is that the former runs before the browser paints changes to the screen, while the latter runs after the paint.
This can be useful when you need to make sure that some DOM changes have taken place before running your side effect. For example, if you need to measure the height of a DOM element, you would use useLayoutEffect
to ensure that the height is calculated accurately.
Here's a simple example to demonstrate the use of useLayoutEffect
:
import React, { useState, useLayoutEffect } from "react";
function MyComponent() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
setWidth(window.innerWidth);
}, []);
return <div>Window width: {width}</div>;
}
Explanation
- In this example, we use
useLayoutEffect
to set the width state to the value of the window's inner width. The effect will only run once on mount, as specified by the second argument[]
. The result of the effect is that the width state is updated immediately after the component has been mounted, allowing us to display the window width.
Conclusion
React hooks allow developers to add state and other features to functional components, making them more versatile and maintainable.
Hooks simplify the code by breaking it into smaller, reusable chunks, leading to improved readability.
We learned about React hooks like
useState
,useEffect
,useContext
,useReducer
,useCallback
,useMemo
anduseLayoutEffect
.We can create our custom hooks to make the code more readable and maintainable that we will learn in the upcoming blogs.
Hope you learned things in better way and if you have any doubts, please ask me in the comment section.
More from the blog
Try to use the codes of the blog in your local IDE and then check how things are working, that will make things more clear to you.
First, read the code, then read the explanation and after that reading the code again will make you understand things more easily.
If you find the blog useful, upvote the blog and share with your friends.
Keep Learning! bye.