How to Maintain the Performance of a React Application on a Daily Basis

kate_astrid

kate astrid

Posted on September 6, 2023

How to Maintain the Performance of a React Application on a Daily Basis

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 and useEffect 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 are useCallback and useMemo, which memoize values, storing them in memory. On a larger scale, this can have an impact on performance. The same goes for React.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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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 divs 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>
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode
  • Prefer Simple divs to Box 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'
}
Enter fullscreen mode Exit fullscreen mode
  • 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';
Enter fullscreen mode Exit fullscreen mode
  • 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 (/* ... */);
};
Enter fullscreen mode Exit fullscreen mode

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 =).

💖 💪 🙅 🚩
kate_astrid
kate astrid

Posted on September 6, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related