I've been analyzing many React Native JavaScript bundles (which you can find on Twitter: https://x.com/tell_me_mur) and have noticed a critical mistake that almost everyone is making. This error significantly impacts your application's App Start Time (AST) and memory usage.
The screenshot below illustrates how the JS bundle is overloaded with numerous i18n locales and Lottie animations (JSON files).
Do you need all locales and animations when the user launches the application? The answer is NO.
You'll only need JSON files at specific moments when using your application. Therefore, it's best to handle them as assets and load them on demand.
How to Load Lottie JSON as Assets On Demand
Move JSON files to the assets directory.
For Android, move all JSON animation files to:
android/app/src/main/assets/*.json
For iOS applications, you can reuse the same files from the Android directory for linking on iOS. To do this, run the following command:
npx react-native-asset --ios-assets android/app/src/main/assets
The files will be successfully added and ready for use. Remember to rebuild your application if you are in development mode.
Read the asset files.
Your project might already have a library for file operations, such as react-native-fs
, react-native-blob-util
, or react-native-file-access
. If not, install one of your choice.
Below are code examples for each of these libraries:
react-native-fs
import { Platform } from 'react-native';
import { readFileRes, readFile, MainBundlePath } from 'react-native-fs';
export async function readAsset(name: string): Promise<string | null> {
try {
if (Platform.OS === 'android') {
const content = await readFileRes(name, 'utf8');
return content;
}
const path = `${MainBundlePath}/${name}`;
const content = await readFile(path, 'utf8');
return content;
} catch (e) {
console.error('Error reading asset:', e);
return null;
}
}
// Usage example
readAsset('animation.json')
.then(jsonStr => JSON.parse(jsonStr))
.then(data => {
// your logic
});
react-native-blob-util
import RNFetchBlob from 'react-native-blob-util';
export async function readAsset(name: string): Promise<string | null> {
try {
const path = RNFetchBlob.fs.asset(name);
const content = await RNFetchBlob.fs.readFile(path, 'utf8');
return content;
} catch (e) {
console.error('Error reading asset:', e);
return null;
}
}
// Usage example
readAsset('animation.json')
.then(jsonStr => JSON.parse(jsonStr))
.then(data => {
// your logic
});
react-native-file-access
import { Platform } from 'react-native';
import { Dirs, FileSystem } from 'react-native-file-access';
export async function readAsset(name: string): Promise<string | null> {
try {
if (Platform.OS === 'android') {
const tmpPath = Dirs.CacheDir + `/${name}`;
await FileSystem.cpAsset(`raw/${name}`, tmpPath, 'resource');
const content = await FileSystem.readFile(tmpPath, 'utf8');
return content;
}
const path = `${Dirs.MainBundleDir}/${name}`;
const content = await FileSystem.readFile(path, 'utf8');
return content;
} catch (e) {
console.error('Error reading asset:', e);
return null;
}
}
// Usage example
readAsset(
Platform.select({
ios: 'animation.json',
android: 'animation', // skip extension for Android
}),
)
.then(jsonStr => JSON.parse(jsonStr))
.then(data => {
// your logic
});
Now you understand how easy it is to read your asset files. From here, you can load these files at any time, which will significantly accelerate your app's start time and reduce memory consumption.
It's also worth mentioning that you can employ various approaches for how and when to load your assets. For instance, you might preload all necessary files at a specific initial stage. Additionally, caching the results can prevent the need to read and parse JSON multiple times.
Render lazy loaded assets
I can also share a React component example showcasing how to implement data loading using the use
React hook and Suspense:
import { readAsset } from './my-fs.js';
import { MyLottie } from './my-lottie.js';
import { Suspense, use } from 'react'; // Assuming 'use' and 'Suspense' are imported.
const sourcePromise = readAsset('new-chat-anim.json').then(s => JSON.parse(s));
export default function App() {
return (
<Suspense fallback={/* ... */}>
<MyLottie sourcePromise={sourcePromise} />
</Suspense>
);
}
// ./my-lottie.js
import LottieView from 'lottie-react-native';
// Assuming 'use' is imported from 'react' as well.
export function MyLottie({ sourcePromise }) {
const source = use(sourcePromise);
return <LottieView source={source} />;
}
As an alternative solution, you can also load data over the internet. For example, Re.Pack offers remote assets functionality.
Lastly, the example involving locales JSON files will be discussed in a dedicated article, as previously detailed in 🚨 99% of React Native Apps Make This Localization (i18n) Mistake — Is Yours One of Them?
Top comments (1)
This was a good article 🤍