Sitemap

Optimize your React / React Native project

4 min readNov 21, 2024

--

Press enter or click to view image in full size
Optimize React Native Project

In this article I’m going to explain about optimizing your react project code for better performance in your application.

Prevent unnecessary re-renders by using memo hook

You can wrap your functional components with React.memo to prevent re-rendering without changing any prop

import React, { useState, memo } from "react";

// Memoized UserProfile component
const UserProfile = memo(({ name, email }) => {
console.log(`Rendering: ${name}`);
return (
<div style={{ border: "1px solid #ccc", margin: "5px", padding: "10px" }}>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
});

const App = () => {
const [users] = useState([
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
{ id: 3, name: "Charlie", email: "charlie@example.com" },
]);
const [counter, setCounter] = useState(0);

return (
<div>
<h1>Memorized user list</h1>
<button onClick={() => setCounter(counter + 1)}>Increment Counter</button>
<p>Counter: {counter}</p>
<div>
{users.map((user) => (
<UserProfile key={user.id} name={user.name} email={user.email} />
))}
</div>
</div>
);
};

export default App;

As per above example UserProfile component will only re-render when name and email props changed. You can check the behaviour by running this react app and clic on counter button. You can see Clicking Increment Counter does not re-render the UserProfile components, as their props are unchanged.

useCallback and useMemo memorizing functions and memorizing computations

  • useCallBack will stop re-creation functions on every render.
  • useMemo memorize expensive computations to avoid re-calculating during every re-render.
import React, { useState, useMemo, useCallback } from "react";

// Component to render individual list items
const ListItem = React.memo(({ item, onSelect }) => {
console.log(`Rendering item: ${item.name}`);
return (
<div onClick={() => onSelect(item)} style={{ padding: "5px", borderBottom: "1px solid #ccc" }}>
{item.name}
</div>
);
});

const App = () => {
const [query, setQuery] = useState("");
const [selectedItem, setSelectedItem] = useState(null);

const items = [
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
{ id: 3, name: "Cherry" },
{ id: 4, name: "Date" },
{ id: 5, name: "Elderberry" },
];

// useMemo to memoize the filtered items based on the search query
const filteredItems = useMemo(() => {
console.log("Filtering items...");
return items.filter((item) => item.name.toLowerCase().includes(query.toLowerCase()));
}, [query, items]);

// useCallback to memoize the select handler to avoid re-creation on every render
const handleSelect = useCallback((item) => {
setSelectedItem(item);
console.log(`Selected item: ${item.name}`);
}, []);

return (
<div style={{ padding: "20px" }}>
<h1>useMemo and useCallback Example</h1>
<input
type="text"
placeholder="Search items..."
value={query}
onChange={(e) => setQuery(e.target.value)}
style={{ padding: "5px", marginBottom: "10px", width: "100%" }}
/>
<div>
{filteredItems.map((item) => (
<ListItem key={item.id} item={item} onSelect={handleSelect} />
))}
</div>
{selectedItem && <h2>Selected: {selectedItem.name}</h2>}
</div>
);
};

export default App;

Explanation of above code :

1. useMemo:

  • Avoids re-filtering the list unless query or items changes.
  • This is especially useful when dealing with large datasets.

2. useCallback:

  • Prevents recreation of the handleSelect function unless its dependencies change.
  • Helps avoid unnecessary re-renders of the ListItem components.

3. React.memo:

  • Optimizes ListItem to only re-render if its item or onSelect props change.

Lazy Loading

This means simply load components only when they are needed.

import React, { lazy, Suspense, useState } from "react";

// Lazy load the Settings component
const Settings = lazy(() => import("./Settings"));

const App = () => {
const [showSettings, setShowSettings] = useState(false);

return (
<div style={{ padding: "20px" }}>
<h1>Lazy Loading Example</h1>
<button onClick={() => setShowSettings(!showSettings)}>
{showSettings ? "Hide" : "Show"} Settings
</button>

{/* Lazy load Settings only when needed */}
{showSettings && (
<Suspense fallback={<div>Loading Settings...</div>}>
<Settings />
</Suspense>
)}
</div>
);
};

export default App;

Lazy: This will dynamically load the settings screen

Get Buwaneka Sumanasekara’s stories in your inbox

Join Medium for free to get updates from this writer.

Suspense will provide fallback option (Loading view)

Use efficient state management

Avoid frequent re-renders dut to state changes by batching state updates. you can use something like this approch

setState(prevState=>({...prevState, newValue)

Use Virtualized Lists

For large datasets, use libraries like react-virtualized to render only visible elements. In React native we can use something like Flatlists and SectionLists

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

const ChatApp = () => {
// Sample large dataset representing chat messages
const messages = Array.from({ length: 1000 }, (_, index) => ({
id: index.toString(),
text: `Message ${index + 1}`,
sender: index % 2 === 0 ? "User A" : "User B",
}));

// Render each message
const renderItem = ({ item }) => (
<View style={styles.message}>
<Text style={styles.sender}>{item.sender}:</Text>
<Text style={styles.text}>{item.text}</Text>
</View>
);

return (
<FlatList
data={messages}
keyExtractor={(item) => item.id}
renderItem={renderItem}
initialNumToRender={20} // Initial items rendered
maxToRenderPerBatch={20} // Max items rendered per batch
windowSize={5} // Items to keep in memory
/>
);
};

const styles = StyleSheet.create({
message: {
backgroundColor: "#e0e0e0",
padding: 10,
marginVertical: 5,
borderRadius: 5,
},
sender: {
fontWeight: "bold",
},
text: {
fontSize: 16,
},
});

export default ChatApp;

We can apply this to kind of real world scenarios like chat application and social app’s home post screen.

As per above code you can see ,

FlatList renders only visible messages plus a few extra, saving memory and improving performance for long chat histories. so its called optimizing rendering.

On the other-hand following things come up with performance

  • initialNumToRender and maxToRenderPerBatch control how many messages are loaded at once.
  • windowSize determines how many items remain in memory.

--

--

Buwaneka Sumanasekara
Buwaneka Sumanasekara

Written by Buwaneka Sumanasekara

Tech Lead | React Native | React JS | Next JS | Remix | Laravel