Part 1 can be found here if you haven't read it yet
useRef:
Basic
useRef
is a React Hook that lets you reference a value that’s not needed for rendering.
const ref = useRef(initialValue)
The ref
variable will be an object with a current
property that is set to "initial value".
It's worth noting that the initialValue
argument is optional, if you don't provide an initial value, the current
property will be undefined
initially. This is because the useRef
hook doesn't set a default value like useState
does.
Dive deeper
The useRef
hook in React is used to access and manipulate the DOM or other elements outside of the React component's rendered output. It creates and returns a reference object, which can be used to store a value or a reference to an element in the DOM, and this value will persist across re-renders of the component.
import { useRef } from 'react';
function Example() {
const inputRef = useRef(null);
console.log(inputRef);//{current: null}
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>Focus Input</button>
</>
);
}
In this example, we're using useRef
to create a reference to an input element. The useRef
hook is called in the component's body and it returns an object with a single property called current
. Initially, the current
property is set to null
, but we can assign it a value later on.
We're then passing the inputRef
object as a ref
prop to the input element, this way React can associate the input element with the inputRef
object. By doing so, we can access the input element later on by calling inputRef.current
.
We also have a button that when it's clicked, it calls the handleClick
function, inside this function we are using the current
property to call the focus method of the input element, this way when the button is clicked the input will be in focus.
It's important to note that the useRef hook doesn't cause a re-render when the value of the current property changes, it's used to retain the values across re-renders.
Also, it's important to notice that the useRef
hook can be used to store references to any type of value, not only DOM elements, you can use it to store references to any type of object, like a web worker, a timer, a network connection and more.
Example
The component renders a button with an onClick event that calls the handleClick
function. The function updates the current value of the ref object by adding 1 to it, and then it shows an alert message displaying the number of clicks. The button also displays the current value of the ref object.
Observe carefully when you click on a button, it will never display a new value on the button; that's where the two pitfall lies, and what you should know before using useRef:
🚩 Don't write a ref during rendering
myRef.current = 173;
🚩 Don't read a ref during rendering
<button onClick={handleClick}> {ref.current} Click me! </button>
You can read or write refs from event handlers or effects instead.
- ✅ effects
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 173;
});
✅ Event Handlers
function handleClick() { // ✅ You can read or write refs in event handlers doSomething(myOtherRef.current); }
It's important to note that useRef is not reactive, meaning that if the value stored in a ref changes, it doesn't trigger a re-render of the component, so it should be used only when you need to store a value that doesn't change often.
useMemo
Basic
useMemo
is a React Hook that lets you cache the result of a calculation between re-renders.
const cachedValue = useMemo(calculateValue, dependencies)
This is the basic syntax for using the useMemo
Hook. The first argument passed to useMemo is the function that performs the calculation, and the second argument is an array of dependencies. The result of the calculation will be cached and returned by useMemo. If any of the dependencies change, the calculation will be run again and the result will be cached again.
Dive deeper
useMemo
is a React hook that allows you to memoize a value. This means that the value is only recalculated if one of the dependencies has changed. This can be useful for performance optimization, as it allows you to avoid unnecessary re-rendering of a component.
For example, if you have a component that performs a complex calculation each time it renders, you can use useMemo
to memoize the result of that calculation. This way, if the component's props or state do not change, the result of the calculation will be reused, and the component will not need to perform the calculation again.
import { useMemo } from 'react';
function MyComponent({ data }) {
const memoizedValue = useMemo(() => {
// Perform a complex calculation with data
return someCalculation(data);
}, [data]);
return <div>{memoizedValue}</div>;
}
In this example, the useMemo hook is used to memoize the result of the someCalculation
function, which is passed the data
prop as an argument. The hook takes a function as its first argument and an array of dependencies as its second argument. The dependencies in this case is data
props.
In this way, React will only call the someCalculation
function and update the memoizedValue
when the data
prop changes. If the data
prop does not change, the previous result will be used, and the component will not need to perform the calculation again.
Usage
useMemo
is commonly used in performance-sensitive components that need to compute derived data based on props or state. A common example is a component that renders a large table or list, where the rows are built from a large dataset that is passed as props. Without useMemo, the component would need to re-compute the rows every time the component re-renders, even if the dataset hasn't changed.
Using useMemo
to memoize the calculation of the rows means that the component only re-computes the rows if the dataset has changed, resulting in a significant performance boost.
Example 1 :
import { useMemo } from 'react';
function Table({ data }) {
const rows = useMemo(() => {
// Calculate the rows for the table based on the data
// This calculation could be expensive, so we only want to do it if the data has changed
return data.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.age}</td>
<td>{item.address}</td>
</tr>
));
}, [data]);
return (
<table>
<tbody>{rows}</tbody>
</table>
);
}
In this example, the component will only re-calculate the rows if the data prop changes. This can be a big performance boost if the data prop is large and the calculation of the rows is expensive.
useMemo is important for performance optimization as it allows to avoid unnecessary re-renders and calculations if the data is not changed. It is especially useful in cases where the component is rendering a large amount of data and the calculation of that data is expensive. By memoizing the calculation, we can ensure that the component is only re-calculating when it needs to, which can greatly improve the performance of the application.
Example 2 :
In this example, the expensiveCalculation function is only called when the values of a
and b
change. Without useMemo, this function would be called on every render, causing a significant performance hit. By caching the result of the calculation with useMemo, we ensure that the calculation is only performed when necessary.
By default, when a component re-renders, React re-renders all of its children recursively.
This can be inefficient if the children are expensive to render, or if they don't need to change.
For example, consider a component that renders a list of items. Each item has a title and a description. The title is passed in as a prop, but the description is calculated based on the title.
function Item({ title }) {
const description = useMemo(() => calculateDescription(title), [title]);
return (
<div>
<h3>{title}</h3>
<p>{description}</p>
</div>
);
}
In this example, the calculateDescription
function is a relatively expensive operation. If the parent component re-renders and passes a new title prop to the Item
component, React will re-render the Item
component and call calculateDescription
again.
By wrapping the calculateDescription
call in a useMemo
hook, we can tell React to only re-run the function and re-render the component if the title
prop has changed. This can improve the performance of our application.
It's important to note that useMemo
only applies to a component's own render, not to the renders of its children. If a component using useMemo re-renders and the memoized value changes, all its children will re-render as well.
Memoizing a dependency of another Hook
import { useMemo } from 'react';
function MyComponent({ data, filter }) {
// Create the filter object directly in the component body
const filterOptions = {
name: filter.name,
age: filter.age,
};
// Use the filterOptions object as a dependency for useMemo
const filteredData = useMemo(() => {
// Perform the filtering calculation
return data.filter(item => {
return item.name === filterOptions.name && item.age === filterOptions.age;
});
}, [data, filterOptions]);// 🚩 Caution: Dependency on an object created in the component body
return (
<div>
{filteredData.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
In this example, the filterOptions
object is created directly in the component body and is used as a dependency for the useMemo
hook. This can cause unnecessary re-renders because the filterOptions
object will be different on every re-render. A better approach would be to extract the creation of the filterOptions
object into a separate function that is only called when the filter values actually change, and use the returned object as the dependency for the useMemo
hook.
function createFilterOptions(filter) {
return {
name: filter.name,
age: filter.age,
};
}
function MyComponent({ data, filter }) {
const filterOptions = useMemo(() => createFilterOptions(filter), [filter]);
const filteredData = useMemo(() => {
return data.filter(item => {
return item.name === filterOptions.name && item.age === filterOptions.age;
});
}, [data, filterOptions]);//// ✅ Only changes when filterOptions or data changes
return (
<div>
{filteredData.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
This way, the filterOptions object is only recreated when the filter values change, and the filteredData calculation will only re-run when the data or filterOptions change.
Conclusion
In conclusion, React hooks have revolutionized the way we think about and write React components. The useRef and useMemo hooks are just as powerful as useState and useEffect. The useRef hook allows us to create references to DOM nodes, making it easy to access and manipulate them directly. The useMemo hook enables us to cache the results of calculations, helping us to optimize our components' performance by only recalculating when necessary. With a deeper understanding of these hooks, we can write more efficient and maintainable code. In my next blog, I will delve deeper into the world of React hooks and explore some of the other important hooks that are available to us. Stay tuned for more insights on the power of React hooks!