Improving Application Performance with Preloading in React and React Native
In today’s fast-paced digital landscape, an application's success hinges on its performance and load times. Users expect smooth, responsive interfaces, and even minor delays can lead to frustration or disengagement. React and React Native provide robust tools for optimizing performance, such as lazy loading components with Suspense
. However, even lazy loading has its drawbacks—particularly the noticeable "white screen" or fallback UI users encounter while components load.
Imagine your app has two modules: Auth and Dashboard. Both modules are lazy-loaded components. After a user registers and navigates to the dashboard, they might face a delay during which the dashboard component is still being downloaded. This can disrupt the user experience.
So how can we avoid this delay and ensure the dashboard loads instantly when users navigate to it? The answer lies in preloading strategies.
What Is Preloading?
Preloading involves downloading components or assets in the background before they are required. By leveraging preloading, we can ensure that components are ready to render immediately when needed. This article demonstrates how to implement a reusable preloading mechanism in React and React Native.
Reusable Preloading Logic
To simplify preloading and lazy loading, let’s create two utilities:
LazyLoader
: A wrapper to handle lazy-loaded components with a fallback UI.lazyWithPreload
: A utility to lazy-load components and provide a preload method.
LazyLoader.tsx
This utility wraps a lazy-loaded component with Suspense
, providing a smooth fallback UI.
import React, { Suspense, ComponentType } from 'react'
import { View, ActivityIndicator, StyleSheet } from 'react-native'
/**
* A wrapper to handle lazy-loaded components with a fallback.
*
* @param {React.ComponentType} component - The lazy-loaded component to render.
* @param {React.ReactNode} fallback - Optional fallback component.
*/
export const LazyLoader = ({
component,
fallback,
}: {
component: ComponentType<any>
fallback?: React.ReactNode
}) => {
const Component = component
return (
<Suspense
fallback={
fallback || (
<View style={styles.container}>
<ActivityIndicator size="large" color="#999" />
</View>
)
}
>
<Component />
</Suspense>
)
}
const styles = StyleSheet.create({
container: { alignItems: 'center', flex: 1, justifyContent: 'center' },
})
lazyWithPreload
Function
This utility extends React's lazy
function, adding a preload
method to download the component in advance.
import React, { lazy } from 'react'
/**
* Extended LazyExoticComponent type with a preload method.
*/
type LazyWithPreload<T extends React.ComponentType<any>> =
React.LazyExoticComponent<T> & {
preload: () => Promise<{ default: T }>
}
/**
* A utility for lazy-loading and preloading React components.
*
* @param factory - The dynamic import function for the component.
* @returns The lazy component with an added preload method.
*/
export const lazyWithPreload = <T extends React.ComponentType<any>>(
factory: () => Promise<{ default: T }>,
): LazyWithPreload<T> => {
const Component = lazy(factory) as LazyWithPreload<T>
Component.preload = factory // Attach the preload method
return Component
}
Usage Examples
React Example
Dashboard.js
import React from 'react'
const Dashboard = () => {
return <div>Welcome to the Dashboard!</div>
}
export default Dashboard
Main.js
import React, { useEffect } from 'react'
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom'
import { lazyWithPreload } from './lazyWithPreload'
import { LazyLoader } from './LazyLoader'
// Lazy load the Dashboard component with preloading capability
const Dashboard = lazyWithPreload(() => import('./Dashboard'))
const App = () => {
useEffect(() => {
// Preload the Dashboard component when the app starts
Dashboard.preload()
}, [])
return (
<Router>
<nav>
<Link to="/dashboard">Go to Dashboard</Link>
</nav>
<Switch>
<Route
path="/dashboard"
render={() => <LazyLoader component={Dashboard} />}
/>
</Switch>
</Router>
)
}
export default App
React Native Example
Dashboard.tsx
import React from 'react'
import { View, Text, StyleSheet } from 'react-native'
const Dashboard = () => {
return (
<View style={styles.container}>
<Text>Welcome to the Dashboard!</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
})
export default Dashboard
App.tsx
import React, { useEffect } from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import { lazyWithPreload } from './lazyWithPreload'
import { LazyLoader } from './LazyLoader'
// Lazy load the Dashboard component with preloading capability
const Dashboard = lazyWithPreload(() => import('./Dashboard'))
const Stack = createStackNavigator()
const App = () => {
useEffect(() => {
// Preload the Dashboard component when the app starts
Dashboard.preload()
}, [])
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Dashboard">
{() => <LazyLoader component={Dashboard} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)
}
export default App
Benefits of Preloading
- Instant Navigation: Eliminates the wait time when users navigate to a new page or screen.
- Improved User Experience: Reduces visual disruptions like fallback UIs.
- Proactive Loading: Ensures critical components are ready when needed.
By integrating lazy loading with a preloading strategy, your React and React Native applications can deliver a smoother, faster experience that users will appreciate.