Measuring React styled-components performance & best practices

useanvil

Anvil Engineering

Posted on May 5, 2022

Measuring React styled-components performance & best practices

Nothing feels worse to a new user than having to navigate across a slow-performing webapp. Nine out of ten times, I hit 'back' on a webpage once I realize a page is rendering excruciatingly slow. Webapp performance is one of the most important components of user experience, and that is why search engines take into account website metrics such as 'first contentful paint' or 'time to interactive' when ranking.

Lighthouse metrics
Lighthouse metrics for an example webpage

I'll assume you already have some working knowledge on what styled-components is and how it works. But just in case, styled-components is one of the most popular open-source styling libraries, especially in the React ecosystem. Instead of applying styles to an entire webpage or specific HTML elements using the class/id system, you can apply styles onto individual React components. One of my favorite aspects of the styled-components approach is it neatly blends together logic & styling – JS, HTML, and CSS – so everything is accessible from just one file.

const StyledButton = styled.button`
  font-size: 14px;
  color: #525252;
  background-color: #7AEFB2;
  border-color: transparent;
`

const ButtonComponent = ({ isLoading, onClick, children }) => {
  return (
    <StyledButton className=example-btn onClick={onClick}>
      {children}
      {isLoading ? <LoadingSpinner /> : null}
    </StyledButton>
  )
}

export default ButtonComponent
Enter fullscreen mode Exit fullscreen mode

Example styled component button in React

Now that we've covered the background, let's ask the important questions. How can we gauge how well our styled-components are performing? What metrics should we look out for? And what are some best practices we can implement to keep our code efficient?

Measuring performance

We'll be using Chrome DevTools' Performance Monitor to gauge a page's performance in real time. It will appear like this:

Performance monitor
Chome DevTools performance monitor

Navigate to the page containing your code, open up the performance monitor, press record, perform an action, and stop recording. You'll see something like this:

Performance timeline & summary
Performance timeline & summary

Looking at the summary, we can see scripting takes up the majority of the recording time – 1904ms out of 2880ms. It appears we can make the most significant improvements in this department. Let's dive further by clicking into the 'Bottom-up' tab.

Performance bottom-up tab
Performance Bottom-up tab

The 'insertBefore' scripting activity takes 364.4ms – the longest of any process. Let's figure out where this code comes from.

Performance bottom-up tab details
insertBefore subfolders

The code with the greatest 'Self time' comes from styled-components. Now that we've identified where the problem lies, let's fix it by making our code more efficient.

To learn more about using the DevTools performance monitor, check out this blog post about optimizing render performance!

Best practices

The first step in code optimization is to examine the way our code is structured. Let's take a look at some of those best practices for styled-components.

Dynamic styling

Oftentimes we want the styling of a UI component to be dependent upon some logic or state in the application. For example, we may want the background of a div to be gray when being hovered over. We can achieve this by applying dynamic styling.

const Field = styled.div`
  background: ${props => props.isHover ? '#E2EEF0' : '#FFFFFF'};
`
Enter fullscreen mode Exit fullscreen mode

Example of dynamic styling - div with light cyan background upon hover

What if we want multiple dynamic styles to be applied? It could look quite repetitive.

const Field = styled.div`
  color: ${props => props.isSelected ? '#2D2D2D' : '#7A7A7A'};
  border-radius: ${props => props.isSelected ? '4px' : '0px'};
  background: ${props => props.isHover ? '#E2EEF0' : '#FFFFFF'};
`
Enter fullscreen mode Exit fullscreen mode

Multiple dynamic styles - the 'meh' way

Let's clean up our code by importing the props once for each prop instead of doing it on a per-line basis.

const Field = styled.div`
  color: #7A7A7A;
  border-radius: 0px;
  background: #FFFFFF;

  ${({ isSelected }) => isSelected && `
    color: #2D2D2D;
    border-radius: 4px;
  `}

  ${({ isHover }) => isHover && `
    background: #E2EEF0;
  `}
`
Enter fullscreen mode Exit fullscreen mode

Multiple dynamic styles - the okay way

Having a bunch of dynamic styles can quickly get complicated. Let's imagine we have a styled-component that takes a 'displayStyle' prop that applies various combinations of CSS. Like so:

const StyledInput = styled.input`
  font-size: 14px;
  border-radius: 2px;

  ${({  displayStyle }) => displayStyle === 'compact' && `
    border-top: none;
    border-left: none;
    border-right: none;
    padding-top: 0;
    padding-left: 0;
    padding-right: 0;
    margin-left: 0;
    margin-right: 0;
    font-size: 12px;
    box-shadow: none;
  `}

  ${({ displayStyle }) => displayStyle === 'internal' && `
    border: none;
    margin-left: 0;
    margin-right: 0;
    font-weight: bold;
  `}

  ${({ displayStyle }) => displayStyle === 'large' && `
    border: 2px;
    margin-left: 10px;
    margin-right: 10px;
    font-size: 22px;
  `}
  …
`
Enter fullscreen mode Exit fullscreen mode

Another example of multiple dynamic styles - the okay way

It can get quite confusing to track all the CSS rules when there are a bunch of different display styles. We can compartmentalize everything by creating distinct styled-components for each display style.

const StyledInput = styled.input`
  font-size: 14px;
  border-radius: 2px;
`

const CompactInput = styled(StyledInput)`
  border-top: none;
  border-left: none;
  border-right: none;
  padding-top: 0;
  padding-left: 0;
  padding-right: 0;
  margin-left: 0;
  margin-right: 0;
  font-size: 12px;
  box-shadow: none;
`

const InternalInput = styled(StyledInput)`
  border: none;
  margin-left: 0;
  margin-right: 0;
  font-weight: bold;
`

const LargeInput = styled(StyledInput)`
  border: 2px;
  margin-left: 10px;
  margin-right: 10px;
  font-size: 22px;
`

export default function Input ({ displayStyle, props }) {
  let InputComponent = StyledInput
  if (displayStyle === 'compact') InputComponent = CompactInput
  else if (displayStyle === 'internal') InputComponent = InternalInput
  else if (displayStyle === 'large') InputComponent = LargeInput

  return (
    <InputComponent {...props} />
  )
}
Enter fullscreen mode Exit fullscreen mode

Multiple dynamic styles - the cleanest way

By adopting this improved format of structuring your styled-components, I hope you'll see some improvement in performance.

Global styles

Styled-components has a helper function named createGlobalStyle which generates a special component that handles global styles. The function works by creating a HTML style tag. Whenever a React component with createGlobalStyle is mounted, createGlobalStyle is called and a new style tag is generated. Using the helper function with a React component that is mounted & unmounted frequently will lead to redundant style tags in the DOM, so it is best to minimize the number of times the function is used.

const DropdownGlobalStyle = createGlobalStyle`
  .selected-option {
    background-color: #3E3E57;
  }
`

function Dropdown (props) {
  return (
    <>
      
      <DropdownGlobalStyle />
    </>
  )
}

const InputGlobalStyle = createGlobalStyle`
  .error-container {
    color: #FB7578;
  }
`

function Input (props) {
  return (
    <>
      
      <InputGlobalStyle />
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Using createGlobalStyle for multiple components - the bad way

Let's create global styles once only in the App component.

const AppGlobalStyle = createGlobalStyle`
  .selected-option {
    background-color: #3E3E57;
  }

  .error-container {
    color: #FB7578;
  }
`

function App () {
  return (
    <>
      
      <AppGlobalStyle />
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Using createGlobalStyle once in the root component - the better way

Summary

We've covered how to measure the performance of your styled-components and best practices on structuring your code. By implementing these techniques into your development process, you can worry less about having a slow-performing web app!

We've applied these practices to our code at Anvil, and believe sharing our experience helps everyone in creating awesome products. If you're developing something cool with PDFs or paperwork automation, let us know at developers@useanvil.com. We'd love to hear from you.

💖 💪 🙅 🚩
useanvil
Anvil Engineering

Posted on May 5, 2022

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

Sign up to receive the latest update from our blog.

Related