Optimizing Performance in React Native Apps (Expo)

vrinch

Vincent Odukwe

Posted on October 10, 2024

Optimizing Performance in React Native Apps (Expo)

Expo simplifies the React Native development process by providing a managed workflow, handling many native configurations for you. However, with the added convenience, it's still essential to optimize your app to ensure smooth and efficient performance, especially when dealing with large data, animations, or resource-heavy components. In this guide, we'll cover practical ways to improve your Expo app's performance.


1. Use the Right Image Sizes and Formats

Unoptimized images are one of the biggest culprits when it comes to performance issues. In Expo, you can manage your image resources effectively with these tips:

  • Resize images before using them in your app. Large images increase memory usage.
  • Use WebP format for better compression compared to PNG or JPEG. Expo supports WebP on both iOS and Android.

Example with an optimized image:

import { Image } from 'react-native';

<Image source={{ uri: 'https://example.com/myimage.webp' }} style={{ width: 100, height: 100 }} />;
Enter fullscreen mode Exit fullscreen mode

Expo also has a built-in Asset module for preloading images, improving perceived performance by loading images before they're rendered:

import { Asset } from 'expo-asset';

Asset.loadAsync(require('./assets/myimage.webp'));
Enter fullscreen mode Exit fullscreen mode

2. Use React.memo for Performance

Using React.memo in Expo can help prevent unnecessary re-renders of components. This is useful when your component does not need to update unless its props change.

const MyComponent = React.memo(({ title }) => {
  return <Text>{title}</Text>;
});
Enter fullscreen mode Exit fullscreen mode

This is especially beneficial when working with components inside FlatList or ScrollView, where re-renders can occur frequently.


3. Optimize Lists with FlatList

In Expo, always opt for FlatList or SectionList when rendering large lists. These components only render items that are visible on the screen, reducing memory usage and improving performance.

Key optimizations:

  • Use getItemLayout: Pre-calculate item sizes to reduce render times.
  • Control initialNumToRender: Reduce the number of initially rendered items.

Example:

<FlatList
  data={myData}
  renderItem={({ item }) => <Text>{item.name}</Text>}
  keyExtractor={(item) => item.id}
  getItemLayout={(data, index) => ({ length: 50, offset: 50 * index, index })}
/>
Enter fullscreen mode Exit fullscreen mode

4. Optimize Animations with Reanimated and useNativeDriver

Animations can significantly impact performance if not handled properly. Expo provides out-of-the-box support for react-native-reanimated, which offers better performance over the default Animated API.

To ensure that animations run smoothly, use the useNativeDriver option to offload animation calculations to the native thread.

import { Animated } from 'react-native';

Animated.timing(animationValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true,
}).start();
Enter fullscreen mode Exit fullscreen mode

For more complex animations, use react-native-reanimated, which is also compatible with Expo.


5. Optimize Bundles with expo-splash-screen and expo-asset

Loading large assets during app launch can slow down startup times. To manage this efficiently, use expo-splash-screen and expo-asset to preload images and other assets.

In your App.js, configure your splash screen like this:

import { SplashScreen } from 'expo-splash-screen';
import { Asset } from 'expo-asset';

SplashScreen.preventAutoHideAsync();

const cacheResourcesAsync = async () => {
  const images = [require('./assets/icon.png')];
  const cacheImages = images.map(image => Asset.loadAsync(image));
  await Promise.all(cacheImages);
  SplashScreen.hideAsync();
};

cacheResourcesAsync();
Enter fullscreen mode Exit fullscreen mode

This ensures that the app preloads assets before the user interacts with it.


6. Use Hermes with Expo (Bare Workflow)

As of now, Hermes is only available in the Bare Workflow of Expo. If you're using the Bare Workflow for more control over native code, enabling Hermes can provide better JavaScript execution performance, reduce memory usage, and improve startup times.

To enable Hermes in Expo Bare Workflow:

  1. Open android/app/build.gradle and add this:
project.ext.react = [
    enableHermes: true
]
Enter fullscreen mode Exit fullscreen mode
  1. Rebuild the Android app using:
expo run:android
Enter fullscreen mode Exit fullscreen mode

While the Managed Workflow doesn’t yet support Hermes, Expo is working on adding this functionality.


7. Avoid Unnecessary Re-renders

Unnecessary re-renders in Expo can impact performance. A few tips:

  • Avoid inline functions and styles: Define functions and styles outside your render method to prevent creating new instances every render.

Example:

const styles = StyleSheet.create({
  button: {
    backgroundColor: 'blue',
  },
});

const MyComponent = () => (
  <Button title="Click me" style={styles.button} />
);
Enter fullscreen mode Exit fullscreen mode
  • Use useMemo and useCallback to memoize values and functions to avoid unnecessary recalculations.

8. Optimize Expo Push Notifications

Using push notifications via Expo can sometimes increase background processing, especially with frequent or heavy notifications. To mitigate performance issues:

  • Limit the frequency of notifications: Use server-side logic to batch or throttle notifications.
  • Use background notifications: These allow for reduced activity when the app is inactive.

Set up notifications in Expo using the expo-notifications package:

import * as Notifications from 'expo-notifications';

Notifications.scheduleNotificationAsync({
  content: {
    title: "You've got mail! 📬",
    body: 'Here is the notification body',
  },
  trigger: { seconds: 2 },
});
Enter fullscreen mode Exit fullscreen mode

9. Optimize API Calls and Caching

Making too many API requests can reduce performance, especially when dealing with large data sets. Use caching libraries like react-query or Expo’s FileSystem API to cache API results and improve performance.

Example with react-query:

import { useQuery } from 'react-query';

const fetchPosts = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  return res.json();
};

const MyComponent = () => {
  const { data, error, isLoading } = useQuery('posts', fetchPosts);

  if (isLoading) return <Text>Loading...</Text>;
  if (error) return <Text>Error loading data</Text>;

  return (
    <FlatList
      data={data}
      renderItem={({ item }) => <Text>{item.title}</Text>}
      keyExtractor={(item) => item.id.toString()}
    />
  );
};
Enter fullscreen mode Exit fullscreen mode

10. Profile Performance Using Expo Tools

Expo provides some built-in profiling tools to analyze app performance:

  • React DevTools: Allows you to inspect the component tree and detect unnecessary renders.
  • Expo Performance Monitor: Use the performance monitor built into Expo to check frame rates and memory usage.

Conclusion

Optimizing performance in Expo apps is crucial for creating a seamless user experience. By using optimized images, minimizing re-renders, managing state efficiently, and taking advantage of tools like Hermes and react-native-reanimated, you can ensure your Expo app runs smoothly. Keep experimenting with these techniques and tools to maintain a fast and responsive mobile app.


Stay Connected

Thank you for following along with this React Native (Expo) beginner’s guide! If you'd like to stay updated with more content or connect with me, feel free to follow me on:

Happy coding, and I look forward to connecting with you!

💖 💪 🙅 🚩
vrinch
Vincent Odukwe

Posted on October 10, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related