How to Maintain the Performance of a React Application on a Daily Basis
kate astrid
Posted on September 6, 2023
I'm a fast-paced person: I eat quickly, walk briskly, and make decisions in a snap. What I can't stand, though, is a slow app — especially one I'm working on. So, I always find myself compelled to scrutinise its performance: examining how many times it re-renders, the size of the DOM, and the speed of user interactions, among other things. Armed with this data, I incrementally improve the app's performance by refining the codebase in a targeted way.
Now, I'd like to share some strategies that could help enhance your app's performance:
-
Be Mindful of the Hooks You Use in Each Component: From my perspective, this is crucial.
useState
anduseEffect
are the most commonly used hooks, but sometimes they can be eliminated with a bit of refactoring, without sacrificing functionality. Fewer hooks mean fewer re-renders, which in turn means a faster app. Other noteworthy hooks areuseCallback
anduseMemo
, which memoize values, storing them in memory. On a larger scale, this can have an impact on performance. The same goes forReact.memo()
.
Simple example:
Imagine you have a component that tracks a user's first name, last name, and age. A naive approach could be to declare three separate useState
hooks:
const UserProfile = () => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [age, setAge] = useState(0);
// Multiple function calls to update the user profile
const updateUserProfile = () => {
setFirstName('John');
setLastName('Doe');
setAge(30);
};
return (
<div>
<h1>{`${firstName} ${lastName}`}</h1>
<p>Age: {age}</p>
<button onClick={updateUserProfile}>Update Profile</button>
</div>
);
};
Every time each of those hooks is called, the component will re-render, even if it happens simultaneously. We could condense this into a single useState
by using an object:
const UserProfile = () => {
const [user, setUser] = useState({
firstName: '',
lastName: '',
age: 0,
});
// Single function call to update the user profile
const updateUserProfile = () => {
setUser({
firstName: 'John',
lastName: 'Doe',
age: 30,
});
};
return (
<div>
<h1>{`${user.firstName} ${user.lastName}`}</h1>
<p>Age: {user.age}</p>
<button onClick={updateUserProfile}>Update Profile</button>
</div>
);
};
Now, we have only one hook, and only one re-render when the hook is called. Bingo.
- Minimise the Number of DOM Nodes: I always ask myself, "Can this HTML tag be safely removed?" If the answer is yes, I remove it. Fewer nodes lead to better performance.
Simple example:
If you find the code using empty div
s solely for styling purposes, consider using CSS Grid or Flexbox to achieve the same layout, thus reducing the number of DOM nodes.
// Before
<div className="wrapper">
<div className="innerWrapper">
<Component />
</div>
</div>
// After
<div className="optimizedWrapper">
<Component />
</div>
- Favor Class-Based Styling Over Inline Styles: Inline styles are considered new objects on each render, which can make React think something has changed, triggering unnecessary re-renders and affecting performance. It's also worth noting that maintaining styles in separate files helps keep the styling consistent.
Simple example:
// Before
<div style={{ display: 'flex' }}>
// After
<div className='container'>
// In a separate css file:
.container: {
display: flex
}
-
Prefer Simple
div
s toBox
from Material UI or Similar UI Libraries: Native HTML elements generally offer better performance because they don't carry the overhead of additional React components, including lifecycle methods and context consumption.
Simple example:
// Before
<Box p={2}>
// After
<div className="container">
// In a separate css file:
.container: {
padding: '2px'
}
-
Prefer Conditional Classes to Conditional Styles in
makeStyles
from Material UI or Similar UI Libraries: Using predefined classes that are toggled based on conditions can be more performant, as it doesn't generate new classes at runtime.
Simple example:
// Before
const useStyles = makeStyles({
root: {
backgroundColor: props => props.isActive ? 'blue' : 'white',
},
});
// After
const className = isActive ? 'active' : 'inactive';
- Opt for Functional Components: There are several compelling reasons for this. First, functional components are easier for build tools like Webpack to optimize through "tree shaking," which only includes the code that's actually being used in the final bundle. Second, functional components often result in a smaller component tree and fewer lifecycle methods, allowing React's diffing algorithm to make quicker updates to the DOM. Lastly, functional components use less memory than class components, which is especially beneficial in environments where memory is limited.
Simple example:
// Before
class MyComponent extends React.Component {
state = { /* ... */ };
componentDidMount() { /* ... */ }
render() { /* ... */ }
}
// After
const MyComponent = () => {
const [state, setState] = useState(/* ... */);
useEffect(() => { /* ... */ }, []);
return (/* ... */);
};
By giving these tips a go and making them a regular part of your coding routine, you're not just on the path to a speedier React app, but you're also gonna make things a whole lot smoother for your users.
Let's create better apps together =).
Posted on September 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.