eMoosavi
Weekly frontend dev braindumps
Advanced Patterns with React.memo and useCallback
Tooling and Libraries

Advanced Patterns with React.memo and useCallback

Unlocking Optimization through Memoization

Jul 04, 2024 - 02:413 min read

React is all about making your UI more interactive and performant. React.memo and useCallback are two powerful tools in the React ecosystem, but their real potential is often overlooked. This post dives deep into optimizing React components by properly leveraging these hooks.

Basics: React.memo and useCallback

First, let's get familiar with what these hooks do:

React.memo

React.memo is a higher-order component similar to React.PureComponent, but it works with functional components. It helps in preventing unnecessary re-renders of a component when its props have not changed.

import React from 'react';

const UserGreeting = React.memo(({ name }) => {
   console.log('UserGreeting rendered');
   return <div>Hello, {name}!</div>;
});

export default UserGreeting;

useCallback

useCallback returns a memoized version of the callback that only changes if one of the dependencies changes. This can be useful for passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.

import React, { useState, useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return <ChildComponent onClick={increment} />;
}

Practical Use Cases

Let's consider a typical scenario: a list of updated items. The naive approach would often lead to unnecessary re-renders of list items even if they haven't changed.

const List = ({ items }) => {
  return (
    <ul>
      {items.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
};

Each ListItem would re-render whenever the parent List renders. To fix this, we should memoize ListItem.

const ListItem = React.memo(({ item }) => {
  console.log(`Rendered ${item.name}`);
  return <li>{item.name}</li>;
});

Optimizing Callbacks in List Item

Passing callbacks down to child components can also lead to unnecessary re-rendering. The solution is to memoize the callback with useCallback.

const List = ({ items, onDelete }) => {
  const handleDelete = useCallback((id) => {
    onDelete(id);
  }, [onDelete]);

  return (
    <ul>
      {items.map(item => (
        <ListItem key={item.id} item={item} onDelete={handleDelete} />
      ))}
    </ul>
  );
};

Advanced Scenarios: Dynamic Memoization

Conditional Rendering

Sometimes, it may not be enough to always memoize a component. You might want to memoize it based on certain conditions.

const ConditionalComponent = React.memo(({ data }) => {
  // expensive computation
  return <div>{computeExpensiveValue(data)}</div>;
}, (prevProps, nextProps) => {
  return prevProps.id === nextProps.id;
});

Strict Comparison with Custom Hooks

For more complex scenarios, you may want to write a custom hook that checks deep equality.

import { useRef, useEffect } from 'react';
import isEqual from 'lodash/isEqual';

function useDeepMemoize(value) {
  const ref = useRef();

  if (!isEqual(value, ref.current)) {
    ref.current = value;
  }

  return ref.current;
}

By utilizing this custom hook, you can achieve deeper control over when a component should be memoized:

const ListItem = ({ item, someObject }) => {
  const memoizedObject = useDeepMemoize(someObject);
  // ...use memoizedObject safely
};

Addressing Common Pitfalls

Stale Closures

A common issue with useCallback and useMemo is the stale closure problem. To avoid this, always ensure your dependencies list is accurate and complete.

const MyComponent = () => {
  const [state, setState] = useState(0);

  const callback = useCallback(() => {
    console.log(state);
  }, [state]);
};

Over-Optimization

Remember that premature optimization is the root of all evil. Use React.memo and useCallback judiciously. Measure performance and only optimize hotspots.

Conclusion

Mastering React.memo and useCallback can lead to significant improvements in your applications' performance. However, it also requires deep understanding and careful application. Always measure and profile your components to ensure that these optimizations are truly beneficial in your specific context.

Article tags
advanced-techniquesreactreact-hooksperformance-optimization
Previous article

State Management

State Machines with React and XState

Next article

React Components

Crafting Reusable Animations with React and framer-motion