DEV Community

Cover image for Why Every React Native Developer Should Understand useCallback
Ars Dev
Ars Dev

Posted on

Why Every React Native Developer Should Understand useCallback

From the author of the Telegram channel REACT NATIVE HUB.

Join a growing community of React Native Devs! 👆


In React Native development, optimizing performance is crucial for creating responsive and efficient applications. One common challenge developers face is the unnecessary re-creation of functions during component re-renders, which can lead to performance bottlenecks. The useCallback hook in React provides a solution by memoizing functions, ensuring they maintain consistent references between renders. This article explores the useCallback hook, its benefits, and practical applications in React Native.

Understanding the Problem: Function Recreation in React Components

In React, defining functions within a component causes them to be recreated on every render. This behavior can lead to performance issues, especially when these functions are passed as props to child components that rely on reference equality to prevent unnecessary re-renders. For instance, consider a scenario where a parent component passes a callback to a memoized child component

import React, { useState, memo } from 'react'; import { Button, Text, View } from 'react-native'; const ChildComponent = memo(({ onPress }) => { console.log('ChildComponent rendered'); return <Button title="Press me" onPress={onPress} />; }); const ParentComponent = () => { const [count, setCount] = useState(0); const handlePress = () => { console.log('Button pressed'); }; return ( <View> <Text>Count: {count}</Text> <ChildComponent onPress={handlePress} /> <Button title="Increment count" onPress={() => setCount(count + 1)} /> </View> ); }; 
Enter fullscreen mode Exit fullscreen mode

In this example, each time ParentComponent re-renders, a new instance of handlePress is created. Since ChildComponent is memoized using React.memo, it should only re-render when its props change. However, because handlePress is a new function on every render, ChildComponent perceives it as a prop change and re-renders unnecessarily.

Introducing useCallback

The useCallback hook is designed to memoize functions, ensuring that they retain the same reference between renders unless their dependencies change. By wrapping a function with useCallback, React returns a memoized version of the function that only changes if one of the specified dependencies changes. This behavior helps prevent unnecessary re-renders of child components that depend on stable function references.

The syntax for useCallback is as follows:

const memoizedCallback = useCallback(() => { // function logic }, [dependency1, dependency2]); 
Enter fullscreen mode Exit fullscreen mode
  • Function Logic: The code to be executed within the memoized function.
  • Dependencies Array: A list of dependencies that, when changed, will cause the function to be recreated.

Applying useCallback in React Native

To address the earlier issue of unnecessary re-renders, we can utilize useCallback to memoize the handlePress function:

import React, { useState, useCallback, memo } from 'react'; import { Button, Text, View } from 'react-native'; const ChildComponent = memo(({ onPress }) => { console.log('ChildComponent rendered'); return <Button title="Press me" onPress={onPress} />; }); const ParentComponent = () => { const [count, setCount] = useState(0); const handlePress = useCallback(() => { console.log('Button pressed'); }, []); return ( <View> <Text>Count: {count}</Text> <ChildComponent onPress={handlePress} /> <Button title="Increment count" onPress={() => setCount(count + 1)} /> </View> ); }; 
Enter fullscreen mode Exit fullscreen mode

By wrapping handlePress with useCallback and providing an empty dependency array, we ensure that handlePressmaintains the same reference across renders. As a result, ChildComponent will not re-render unless handlePresschanges, thereby improving performance.

Practical Use Cases in React Native

Optimizing List Rendering with FlatList

In React Native, the FlatList component is commonly used to render long lists of data. To optimize performance, it's essential to prevent unnecessary re-renders of item components. By memoizing the renderItem function using useCallback, we can achieve this optimization:

import React, { useCallback } from 'react'; import { FlatList, Text, TouchableOpacity } from 'react-native'; const MyList = ({ data, onItemPress }) => { const renderItem = useCallback( ({ item }) => ( <TouchableOpacity onPress={() => onItemPress(item.id)}> <Text>{item.title}</Text> </TouchableOpacity> ), [onItemPress] ); return <FlatList data={data} renderItem={renderItem} keyExtractor={(item) => item.id.toString()} />; }; 
Enter fullscreen mode Exit fullscreen mode

In this example, renderItem is memoized with useCallback, ensuring that it maintains the same reference unless onItemPress changes. This approach prevents unnecessary re-renders of list items, enhancing the performance of the FlatList.

Handling Event Listeners

When adding event listeners, it's advisable to use stable function references to ensure proper addition and removal of listeners. Using useCallback helps maintain consistent references:

import React, { useEffect, useCallback } from 'react'; import { BackHandler } from 'react-native'; const MyComponent = () => { const handleBackPress = useCallback(() => { // Handle back press return true; }, []); useEffect(() => { BackHandler.addEventListener('hardwareBackPress', handleBackPress); return () => BackHandler.removeEventListener('hardwareBackPress', handleBackPress); }, [handleBackPress]); return ( // Component JSX ); }; 
Enter fullscreen mode Exit fullscreen mode

By memoizing handleBackPress with useCallback, we ensure that the same function reference is used during the addition and removal of the event listener, preventing potential issues with stale references.

Best Practices for Using useCallback

While useCallback is a powerful tool for optimizing React Native applications, it's important to use it correctly to avoid unnecessary complexity. Here are some best practices:

  1. Use it for Performance-Critical Components
    • useCallback is most useful when working with memoized components (React.memo) or lists (FlatListSectionList).
    • If a function does not cause unnecessary re-renders, using useCallback may add unnecessary complexity.
  2. Keep Dependencies Accurate

    • Always include all dependencies in the dependency array to avoid potential bugs caused by stale closures.
    • For example, if a function depends on a piece of state, include it in the dependency array:

      const handlePress = useCallback(() => { console.log(count); }, [count]); // count is a dependency 
  3. Avoid Overusing useCallback

    • Wrapping every function in useCallback is unnecessary and may actually make the code harder to read.
    • Functions that don’t depend on component re-renders don’t need useCallback.
  4. Combine with useMemo When Needed

    • If you’re passing a memoized function to a memoized object or array, use useMemo to prevent unnecessary recalculations:

      const memoizedData = useMemo(() => calculateData(items), [items]); const handlePress = useCallback(() => { console.log('Pressed'); }, []); 

When Not to Use useCallback

While useCallback helps with performance, using it everywhere can make code harder to understand without tangible benefits. Here are cases where you don’t need useCallback:

  • For simple inline event handlers

    <Button title="Click me" onPress={() => console.log('Clicked')} /> 

    The function inside onPress is simple and doesn’t need memoization.

  • For stable functions that don’t cause re-renders

    If a function is defined outside of the component or doesn’t change on re-renders, useCallback isn’t necessary.

  • When optimizing prematurely

    Don’t use useCallback unless there’s a measurable performance issue.

Conclusion

The useCallback hook is a powerful tool for optimizing React Native applications by preventing unnecessary function recreations and improving rendering efficiency. However, it should be used strategically to avoid unnecessary complexity.

Key Takeaways

✔ Use useCallback to prevent unnecessary re-renders in performance-critical components.

✔ Always include all dependencies in the dependency array to avoid stale closures.

✔ Avoid overusing useCallback—only apply it where function references impact performance.

✔ Combine with useMemo for optimal performance in complex scenarios.

By understanding when and how to use useCallback, you can build more efficient and performant React Native applications. 🚀


About me: My name is Arsen, and I am a react native developer and owner of the TG channel 👇

🔗 Join TG community for React Native Devs: REACT NATIVE HUB

Top comments (0)