Turbocharging Your React Apps: A Practical Dive into Performance-Oriented React Hooks

Published: May 20, 2023

Time to read: 4 min

Turbocharging Your React Apps: A Practical Dive into Performance-Oriented React Hooks

Published: May 20, 2023

Time to read: 4 min

Alright, round two of our performance-enhancing React hooks journey. Now, we're going to get our hands dirty with some practical applications. You might want to strap in.

useMemo: Practical Uses

Here's a realistic scenario: let's say you're building an e-commerce app, and you need to display a filtered product list.

You fetch the products:

code block is jsx
let products = fetchProducts();

And here's your filter:

code block is jsx
let filteredProducts = products.filter(product => product.category === selectedCategory);

Without useMemo, this filter operation will run every time your component re-renders, even when products or selectedCategory haven't changed.

However, wrapping the filter operation with useMemo ensures it only runs when it really needs to:

code block is jsx
let filteredProducts = useMemo(() =>
  products.filter(product => product.category === selectedCategory),
  [products, selectedCategory]
);

But beware: useMemo isn't a magic performance potion. Use it only when you're dealing with heavy computational operations. Otherwise, the cost of memory consumption for storing the memoized values can exceed the performance benefits.

useCallback: The Event Listener Scenario

Imagine you have a component that adds an event listener to the window object when it mounts:

code block is jsx
useEffect(() => {
  window.addEventListener('resize', handleResize);
}, []);

If handleResize is defined inside your component and uses some props or state, you would have to include it in your dependency array. And if it changes on every render, it might cause unnecessary effect runs.

Wrapping your function in useCallback will ensure it only changes when its dependencies do:

code block is jsx
const handleResize = useCallback(() => {
  /* Do something with width, height */
}, [width, height]);

useEffect(() => {
  window.addEventListener('resize', handleResize);
}, [handleResize]);

Again, remember that useCallback is not always necessary. It should be used when the cost of recreating the function on each render is higher than the cost of memoizing it.

useReducer: When useState Just Isn't Enough

Imagine you're handling a complex form with many fields and various interactions. Here's a glimpse of what your useState usage might look like:

code block is jsx
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');

And the list goes on. useReducer shines in these scenarios. Instead of managing multiple disjointed state variables, you can manage them all in one place:

code block is jsx
const formReducer = (state, action) => {
  switch(action.type) {
    case 'SET_FIRST_NAME':
      return { ...state, firstName: action.value };
    // ... handle other actions
  }
}

const [formState, dispatch] = useReducer(formReducer, initialFormState);

The form can now handle changes in a uniform manner, and the state management logic is abstracted away from the component.

But don't reach for useReducer each time you see more than two useState calls. It might make your component more complex and harder to understand if the state management isn't complex enough to warrant its use.

React.memo: Expensive Components Scenario

Consider a component in your app that's responsible for rendering a big, complex SVG graph. This component gets re-rendered frequently, even though the props don't change often. In such cases, React.memo can prevent unnecessary renders:

code block is jsx
const Graph = React.memo(function Graph({ data

 }) {
  // expensive computations and render logic
});

// in parent component
<Graph data={myData} />

However, React.memo does a shallow comparison of the props. If your props are complex objects, it might not work as expected. Also, memoizing components that are cheap to render can even negatively impact performance due to the overhead of the comparison.

In summary, the right hook for the right job can make a substantial difference in your React application's performance. Keep an eye on your app's performance metrics, profile when necessary, and make informed decisions about when to use these hooks. Like a master chef, know your ingredients well and choose them wisely for each dish you prepare. Until next time, bon appétit and happy coding!