Patrick Desjardins Blog
Patrick Desjardins picture from a conference

React Dropdown Mounting Optimization from 400ms to 2ms

Posted on: 2018-04-10

I always have the Chrome's developer tool open without exception when developing websites. I am having this habit since the first FireBug extension was released from FireFox a very while ago! Once in a while, I collect performance and look at the result. It keeps me on my toes if regression occurs or merely to have a real picture of what is going on. What I found is that checking performance is even more critical when using React and Redux because many lifecycle events can generate a lot of performance hits. Recently, I noticed that one of the websites I was working was rendering a drop down that was taking 400ms to mount. The drop-down is visible in the main menu of the site hence it's a 400ms that is costly right from the start of the application. I also noticed that the value of this dropdown is change once in a while, thus a very expensive 400ms.

The profiler was very clear about the issue: every item of the drop-down was mounting, and the number of choices was reaching up to a thousand. Not that it is essential, but the control was from React Semantic-UI. The component doesn't have a lazy loading bake-in feature. So, I ended up coding a rapid change to render the drop-down, but no item until the user opens the drop-down.

The hosting React component of the dropdown is receiving by property the list of elements to add in the drop-down. The solution was to instead of to bound the list directly to create a state to the control. The component's state contains two fields. One that indicates if the list of elements is mounted to the control and the actual list of items. Here is the TypeScript's interface that defines the state.

export interface PickerStates<T> { 
  isItemsMounted: boolean; items: T[]; 
} 

The constructor of the hosting React component initializes the state to false and to an empty list.

constructor(props: PickerProps) { 
  super(props); 
  this.state = { 
    isItemsMounted: false, 
    items: [] 
  }; 
} 

The render needs to render the Semantic React Dropdown control. This one leverages its capability of being able to have a caption label to write the actual selected value. This is great since it gives the possibility to have an active element that is not even yet mounted. The options property, which is the list of item, is set to the state which is empty at the first render. The trick is coded in the "onOpen" method which executes code when the user clicks to open the drop-down. It transfers the list into the state. Since the state change, the render is executed again and render the list with all items. The recursive render is executed only once because of the boolean guard and mounts all the items.

<Dropdown 
  item={true} 
  value={this.props.selectedItemId.toString()} 
  options={this.state.items} 
  search={true} 
  onOpen={() => { 
    if (!this.state.isItemsMounted) { 
      this.setState({ isItemsMounted: true, items: this.props.items }); 
    } 
   }
  } 
  text={selectedElementText} 
/> 

A fraction of the users perceive the delay of 400ms and at a time not crucial than the bootstrap of the application. In the end, the justification of using React's state remains adequate by not performing deep logic which keeps the component dummy.