Optimize your React / 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
useCallBackwill stop re-creation functions on every render.useMemomemorize 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
queryoritemschanges. - This is especially useful when dealing with large datasets.
2. useCallback:
- Prevents recreation of the
handleSelectfunction unless its dependencies change. - Helps avoid unnecessary re-renders of the
ListItemcomponents.
3. React.memo:
- Optimizes
ListItemto only re-render if itsitemoronSelectprops 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
initialNumToRenderandmaxToRenderPerBatchcontrol how many messages are loaded at once.windowSizedetermines how many items remain in memory.






