Nov 22, 2022

Memoization, useCallback & memo (using them wisely)

Understand and Implement memoization in React Native and concepts related to Pure Component in class and memo in functional components.

Author

Ishtjot SinghSenior Software Engineer - II
Memoization, useCallback & memo (using them wisely)

In this article, we’ll learn how to implement memoization in React Native and touch base with the major topics around it.

Focus Points of the Blog:

Memoization and its importance.Important concepts related to Pure Component in class and memo in functional components.How to implement memoization.Different use cases with and without memoization.

Let's get started!

Memoization and Its Importance:

Memoization is just caching the result of an expensive function and returning it when the function is called again with the same input and hence improving the performance significantly.

So, memoization is a great optimization technique that enhances an app's performance significantly when used correctly.

Concepts to Know Before Memoization:

  • Pure component:- Pure component was introduced in react to minimize the wasted renders (wasted renders are the re-renders that occur even when the value of the state is not being changed explicitly). So basically, it implicitly has shouldComponentUpdate in PureComponent class in React. So, it does a shallow check If the previous state and props data are the same as the following props or state, the component is not Re-rendered. You can read the article here to learn more about Pure Component.
Now, here is a catch: React.PureComponent is only for class components!

Stay with me; I will tell you how to achieve the same in functional components. 😄

  • Shallow Comparison: Shallow comparison ("===") is used to compare objects, only comparing the type (numbers, strings) and their value, but it is inefficient in the case of deeply nested objects and may result in false positives. So to sum it up - when comparing objects, it does not compare their attributes only their references are compared.
  • Rendering: So rendering of a component occurs whenever there is an update in the props or state of the component. In this process, react picks up the required changes in the ui, current state, and props. But note that the virtual DOM still needs to be changed. (You can read more about Virtual DOM here.)
  • Reconciliation: The process in which a virtual representation of UI is placed in the memory, and the calculation and comparison of the changes are made to be applied to virtual DOM.
  • DOM manipulation: Once React finishes the calculations of the changes needed in the application tree, it will apply all the required changes to the DOM. These changes are applied synchronously, and the DOM is updated. This is called DOM manipulation.
  • Re-rendering: After the first render, re-render is caused when a prop in a component gets updated, when a state in a component gets updated and when a parent component’s render method gets called.

Since we know the core components, we can jump right back to our topic: Memoization!

We will now quickly discuss how you can implement memoization in 3 aspects:-

1. React.Memo():

As I promised earlier, I will tell you a way to achieve shouldComponentUpdate and prevent unnecessary renders in functional components, here is one key to that! React.memo() is a HOC (Higher Order component) that enhances the performance of the functional components.

Note:- React.memo(...) is to functional components what React.PureComponent is to class components. So, if the component is supplied with the same state or the same props, it will skip the re-rendering. No rendering, no reconciliation, just reusing the last rendered result.
  • It does a shallow check so we need to add our comparison function to tell when to update in case of deep nesting.

Let's see an example:

Consider this functional component:

const SimpleComponent = ()=> {
    return (
        <View>
           <Text> I am a Functional component </Text>
        </View>
    )
}

We pass the SimpleComponent as an argument to React.memo():


const SimpleComponent = ()=> {
    return (
        <View>
           <Text> I am a Functional component </Text>
        </View>
    )
}
const MemoizedComponent = React.memo(SimpleComponent);

Now consider this parent-child scenario; in this example, I will also tell you why memoization is important:

import React, { useState } from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";

const ChildComponent = (props) => {
  console.log("Child component rendered");
  return (
    <View>
      <Text>Hi, i am {props?.name} component! </Text>
    </View>
  );
};

function App() {
  const [counter, setCounter] = useState(0);
  console.log("Parent component rendered");

  return (
    <View style={styles.mainContainer}>
      <Text>Hi, i am parent! </Text>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setCounter(counter + 1)}
      >
        <Text>Click me!</Text>
      </TouchableOpacity>
      <ChildComponent name="child" />
    </View>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  button: {
    borderWidth: 1,
    marginVertical: 10,
    paddingHorizontal: 20,
    paddingVertical: 10
  }
});
export default App;

Here is the above code preview:


So in the above code, every time we press the Click me! button, the child component is rendered as well along with the parent as the parent's state is being updated.

We can see here that, even when there was no actual change in the child component, the new virtual DOM was created, and a difference check was performed. For small React components, this performance is negligible, but for large components, the performance impact is significant. To avoid this re-render and virtual DOM check, we use memoization.

So, now just by adding React.memo() to the ChildComponent:

import React, { useState } from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";

const ChildComponent = React.memo((props) => {
  console.log("Child component rendered");
  return (
    <View>
      <Text>Hi, i am {props?.name} component! </Text>
    </View>
  );
});

function App() {
  const [counter, setCounter] = useState(0);
  console.log("Parent component rendered");

  return (
    <View style={styles.mainContainer}>
      <Text>Hi, i am parent! </Text>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setCounter(counter + 1)}
      >
        <Text>Click me!</Text>
      </TouchableOpacity>
      <ChildComponent name="child" />
    </View>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  button: {
    borderWidth: 1,
    marginVertical: 10,
    paddingHorizontal: 20,
    paddingVertical: 10
  }
});
export default App;

Voila! the re-renders for the ChildComponent are gone.

Every time we press Click me! button, apart from the initial render the parent component only gets rendered.

The Problem with React.memo():

In the above example, we saw that when we used the React.memo() , the child component didn’t re-render, even if the parent component did. Now, in the above code if we pass a function as a prop to our ChildComponent, even after using React.memo()the child component will re-render!

import React, { useState } from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";

const ChildComponent = React.memo((props) => {
  console.log("Child component rendered");
  return (
    <View>
      <Text>Hi, i am {props?.name} component! </Text>
    </View>
  );
});

function App() {
  const [counter, setCounter] = useState(0);
  console.log("Parent component rendered");

  return (
    <View style={styles.mainContainer}>
      <Text>Hi, i am parent! </Text>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setCounter(counter + 1)}
      >
        <Text>Click me!</Text>
      </TouchableOpacity>
      <ChildComponent name="child" fun={() => {}} />
    </View>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  button: {
    borderWidth: 1,
    marginVertical: 10,
    paddingHorizontal: 20,
    paddingVertical: 10
  }
});
export default App;

As you see, we just passed an empty function fun as a prop to our ChildComponent, we didn't even use it anywhere, still, our ChildComponent will re-render with every click.

To solve this issue, we have our second key:

2. useCallback():

The main issue that caused the child to re-render is the recreation of the handler function fun. To prevent re-rendering of our ChildComponent we will we will wrap our callback function fun with useCallback. So, our useCallback hook will cache our function fun and will create only a new function when its dependency changes.

Consider the following example:

import React, { useCallback, useState } from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";

const ChildComponent = React.memo((props) => {
  console.log("Child component rendered");
  return (
    <View>
      <Text>Hi, i am {props?.name} component! </Text>
    </View>
  );
});

function App() {
  const [counter, setCounter] = useState(0);
  console.log("Parent component rendered");

  // useCallback
  const funHandler = useCallback(() => {}, []); //dependency array

  return (
    <View style={styles.mainContainer}>
      <Text>Hi, i am parent! </Text>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setCounter(counter + 1)}
      >
        <Text>Click me!</Text>
      </TouchableOpacity>
      <ChildComponent name="child" fun={funHandler} />
    </View>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  button: {
    borderWidth: 1,
    marginVertical: 10,
    paddingHorizontal: 20,
    paddingVertical: 10
  }
});
export default App;

You can have a look at this preview:

now with the use of useCallback(), the ChildComponent will only re-render, when there is any change in the dependency leading to re-creation of the callback function.

Our third and last key is:

3. useMemo():

useMemo is similar to useCallback. The difference being, useMemo memoize the result of the callback function instead of memoizing a callback itself. We have to pass the dependency list to the useMemo as well. So whenever the dependencies change it will call the function again and do a recalculation and memoize new value. This can prevent re-rendering of huge functions and significantly increase the app's performance by just caching the result itself.

Consider this below example:

Without useMemo():

keep an eye at the function sumOfNumbers -

import React, { useCallback, useState } from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";

const ChildComponent = React.memo((props) => {
  console.log("Child component rendered");
  return (
    <View>
      <Text>Hi, i am {props?.name} component! </Text>
    </View>
  );
});

function App() {
  const [counter, setCounter] = useState(0);
  const [numbers, setNumbers] = useState({ Num1: 1, Num2: 2 });
  console.log("Parent component rendered");

  // useCallback
  const funHandler = useCallback(() => {}, []); //dependency array

  const sumOfNumbers = () => {
    console.log("Sum of numbers function is called");
    return numbers.Num1 + numbers.Num2;
  };

  return (
    <View style={styles.mainContainer}>
      <Text>Hi, i am parent! </Text>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setCounter(counter + 1)}
      >
        <Text>Click me!</Text>
      </TouchableOpacity>
      <ChildComponent name="child" fun={funHandler} />
      <Text>Sum of numbers is {sumOfNumbers()}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  button: {
    borderWidth: 1,
    marginVertical: 10,
    paddingHorizontal: 20,
    paddingVertical: 10
  }
});
export default App;

Preview for the code above:

The callback function sumOfNumbers renders every time with the update in the parent component.

with useMemo():

import React, { useCallback, useMemo, useState } from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";

const ChildComponent = React.memo((props) => {
  console.log("Child component rendered");
  return (
    <View>
      <Text>Hi, i am {props?.name} component! </Text>
    </View>
  );
});

function App() {
  const [counter, setCounter] = useState(0);
  const [numbers, setNumbers] = useState({ Num1: 1, Num2: 2 });
  console.log("Parent component rendered");

  // useCallback
  const funHandler = useCallback(() => {}, []); //dependency array

  const sumOfNumbers = useMemo(() => {
    console.log("Sum of numbers function is called");
    return numbers.Num1 + numbers.Num2;
  }, [numbers]);

  return (
    <View style={styles.mainContainer}>
      <Text>Hi, i am parent! </Text>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setCounter(counter + 1)}
      >
        <Text>Click me!</Text>
      </TouchableOpacity>
      <ChildComponent name="child" fun={funHandler} />
      <Text>Sum of numbers is {sumOfNumbers}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  button: {
    borderWidth: 1,
    marginVertical: 10,
    paddingHorizontal: 20,
    paddingVertical: 10
  }
});
export default App;

As you can see, the sumOfNumbers only gets called once.

Great with this, we have unlocked maximum optimisation with memoization and have significantly improved our app's performance!

That was pretty cool, right? Also huge!

Things to Remember:

Memoization is a great solution for improving performance in apps by eliminating wasted renders of a component. You might think of just adding memoization for all the components, but that’s not a good way to build your React components as no doubt this gives us a huge optimization way, but with a compromise in the RAM usage. You should use memoization only in cases where the component:

  • Returns the same output when given the same props
  • Has multiple UI elements, and a virtual DOM check will impact the performance
  • Is often provided with the same props.

Conclusion:

  • Basic react concepts.
  • Importance of memoization.
  • Important concepts related to Pure Component in class and memo in functional components.
  • How to implement memoization.
  • Important use cases covering React.memo(), useCallback(), useMemo() and functions with and without memoization.

References:

RN DocsPure ComponentsReact Docs

I hope this article helped you in some way! If you have any questions regarding this or anything I should add, correct or remove, feel free to comment, email or DM me. Till then stay healthy, keep learning, and keep growing.

Subscribe to Our Newsletter

RELATED ARTICLES

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
Why Legacy Systems Block Real-Time AI Decision-Making
Business

Aug 4, 2026

Why Legacy Systems Block Real-Time AI Decision-Making
Learn how legacy systems limit real-time AI decision-making and what businesses can do to build an AI-ready infrastructure.
What Makes an AI Product Enterprise-Ready? A Business Leader’s Perspective
Business

Aug 4, 2026

What Makes an AI Product Enterprise-Ready? A Business Leader’s Perspective
Most AI pilots never make it to production. Here are the five questions business leaders should ask before approving, buying, or scaling an AI product.
Building AI-Powered Banking CRM Platforms Without Replacing Core Banking Systems
Business

Jul 31, 2026

Building AI-Powered Banking CRM Platforms Without Replacing Core Banking Systems
Banks can modernize CRM with AI without replacing their core banking systems. This guide covers the architecture, use cases, governance, and roadmap to do it.
AI in Fintech: Everyone's Talking, Few are Shipping
Business

Jul 30, 2026

AI in Fintech: Everyone's Talking, Few are Shipping
This blog covers the key engineering, governance, and compliance principles required to build production-ready AI systems for financial services.
ERP and MES Integration for U.S. Pharma Manufacturers: A Roadmap to Achieve Zero-Error Production and End-to-End Traceability
Business

Jul 27, 2026

ERP and MES Integration for U.S. Pharma Manufacturers: A Roadmap to Achieve Zero-Error Production and End-to-End Traceability
A strategic roadmap for U.S. pharma leaders integrating ERP and MES to reduce production errors, accelerate batch release, strengthen compliance readiness, and enable end-to-end traceability across manufacturing sites.
How to Build Medical Device Software with AI: Compliance, Architecture, and Development Process
Business

Jul 24, 2026

How to Build Medical Device Software with AI: Compliance, Architecture, and Development Process
A guide for engineering leaders on building compliant, production-ready AI medical device software, from architecture to FDA clearance.

The Right Conversation Can Save You Six Months.

Whether you’re navigating AI adoption, modernizing legacy systems, or scaling a product - we start by listening. No pitch deck. No template. A real conversation.