CSS Only* Search: Improve React Filtering Performance with CSS!

Rate this content
Bookmark

Working on emoji-picker-react I encountered a challenge - filtering the list of 1800 emojis triggered a significant delay due to re-renders.

Let's learn how I fixed this only using CSS!

11 min
12 Dec, 2023

Video Summary and Transcription

Evitar Alus, a frontend engineer, shares a trick to improve search performance in React applications by optimizing the search algorithm and reducing DOM updates. CSS attribute selectors can be used for filtering and applying styles based on emoji values. A component called CSS Search can be created to improve search performance by hiding emojis that do not match the search query. Despite some performance drawbacks, this solution works significantly faster. Visit Evitar Alus' website or Twitter account for more of their work.

Available in Español

1. Improving Search Performance in React Applications

Short description:

I'm Evitar Alus, a frontend engineer at Meda in Tel Aviv and the author of the open source package emoji-pkreact. I want to share a trick to improve search performance in React applications. Initially, I faced performance issues due to the large number of emojis and the complex state management. The search bar and emojis were distant in the React tree, causing delays. I made improvements to the search algorithm and reduced the DOM updates, resulting in faster performance. Let me show you the difference and explain how I achieved it.

Hey, everybody. How are you? My name is Evitar Alus. I'm a frontend engineer at Meda in Tel Aviv and I'm the author of several open source packages, one of which is this cool little package called emoji-pkreact that allows you to put an emoji-pkreact right inside your React application on the web with ease.

And today I want to share with you a little trick that I came up with to improve search performance in React applications and React packages. And when I started building emoji-pkreact back in 2017-ish, I thought it would be pretty straightforward to build an emoji-pkreact component with all the features of an expected emoji-pkreact that would also be performant and easy to use. And quite immediately I started receiving issues about performance, for example, that search is extremely laggy and, obviously, my response was that it does not reproduce. Obviously, that was wrong.

Search was actually laggy. And the reason for it is quite understandable. You see, we have roughly 1800 different emojis being rendered on the Emoji Picker. And when you interact with each of them, you have some state updates. So, for example, when you hover or when you type something in the search, you make changes to the DOM and to the elements. And when you deal with so many elements, and when they have so many different states that they can be in, it can be quite intense to work with. And especially here, we see the search bar is up here and the emojis are there. Which means that they're very distant in the React tree. Which means that we have to have some almost global Emoji Picker React state to manage the filtering capabilities. And then when we type some character inside of the Emoji Picker, we go update the state, run some search algorithm that I try to make performant, and then React goes and does DOM diffing and then only applies the changes to the DOM, and each of these steps take time. And when we deal with 1800 emojis, that takes a lot of time.

Now, if you look now, it looks pretty immediate. It works pretty fast, but I want to show you just for a second what it felt like before Emoji Picker React had these capabilities and the improved search. So I'll type something and you'll notice slight lag, even on my MacBook Pro. You felt that? There was some lag between my character being typed and the changes appearing on a screen, and that's a high-end MacBook Pro. Let's try to do some slowdowns, so 6x slowdown to simulate an older machine, or a slower machine, and let's see what it actually feels like for a user in the wild. And you saw that, right? That's not ideal. We gotta fix this. Just to show you what actually is going on when we type each character, when we type each character, we actually go out and re-render everything. And the reason is, again, that we have that top level tree as state and that we actually have to modify all the elements on the DOM. But do we, really? And this is what I wanted to find out. Do I really have to manipulate the DOM and change all the emojis just to make them disappear? Or maybe I can somehow skip it? So I went to look at the actual emoji elements that I created and what I have here, each emoji is a button component or a button element and they have an image inside them. They sometimes have a span with an emoji character.

2. Using CSS Attribute Selectors for Filtering

Short description:

It depends on the user configuration. Each emoji has an area label and full name for accessibility and search. We can use CSS attribute selectors to apply styles based on these values. Let's update our search component to use local state instead of the global state. By setting a value, we can apply filters to the emoji picker.

It depends on the user configuration. But each of them has an area label for accessibility, and also the full name for the emoji of the emoji, because an emoji might have multiple names for search. And given the fact that I already have all this information on the DOM, maybe I can make use of it for filtering as well? Apparently I can and apparently it's pretty easy.

In CSS we have something that's called an attribute selector that allows us to select an arbitrary attribute and based on its value apply styles to elements. So this means that I can set a selector based on the full name, the data property or based on the area label. And I just want to show you how it's done and how easy it is to actually implement.

So here is our search component. It works currently by updating the global state but instead let's not do that anymore. And instead let's have some local states. So let's import useState. So import useState from React and then let's set this state. So const value setValue equals useState. It should be undefined and also let's initialize it to undefined or string. Yeah, cool. And now all we have to do here is set a value and if we'll look back at the emoji picker we'll see that typing does not do anything other than just hide the categories. And the reason is because we do not apply anything from it yet.

3. Improving Search Performance with CSS

Short description:

To improve search performance in React applications, create a component called CSS Search that returns a style tag. Use CSS attribute selectors and a search query to hide emojis that do not match the search query. Additionally, utilize the CSS pseudo selector 'Has' to automatically hide categories without matching emojis. Despite the performance drawbacks, this solution works significantly faster and is a good compromise. Throttling the CPU and running the profiler show minimal delays and updates. Thank you for joining me and feel free to check out my website or Twitter account for more of my work.

And what I want to do now is something that everybody will tell you not to do but it actually works pretty well. It's to create another component that returns a style tag. Yes, a style tag, a CSS style. So let's do function and call it CSS Search. And it will take the value which is string or undefined, right? Yeah, sure.

Okay. If we don't have a value, let's return null and otherwise let's return a style element. So return style and it takes a string as its shell string and we have to put some style inside and just to make everything more scoped, let's first take the CSS selector or the class names for the emoji picker just so we apply everything inside the picker itself and not outside. So emoji picker react and emoji picker list. So that's our selector and also let's trim and lower case this and the value of the search value so that we do not deal with case sensitivity issues.

Now we need to write some search query and it's going to be super simple considering the fact that we already know of attributes selector. So what we can do is write a search query. Um, that takes a button and then the data full name. Again, that's the one that holds or, uh, all our emoji names and then we can do a partial match mean that if we have like a part of the name or our search query is in the part of the name of the emoji, like in the beginning in the middle or in the end, it will still be matched, whatever it is that's inside. So for example, if I do smile, if I search for the emoji smile, but only type the character I it will be matched. So this is the search query and what do we want to do now is just tell emojis to hide themselves if they do not match this because otherwise all the other emojis should be displayed. So what we can do is use the search query and say every button and that does not match the search query should set itself to display none, meaning that every emoji that does not fit this, our search query will be, will disappear immediately. Let's just put this inside of our search component and let's see if it works. So let's do a value, pass it the value and go back to the emoji picker and I'll type K and we're starting to see something disappearing, an ING and they're disappearing like search is working. Let's try bike and I'm seeing a tiny issue here, you see, all these empty categories, we did not have them previously but when we had JavaScript, we just removed all the elements of the empty categories. What can we do now? Because at the moment, we're only selecting based on the emoji itself. Well, almost two years ago, I think we got a new CSS selector and new CSS pseudo selector called Has that allows us to set the visibility or set any CSS style for an element based on its children, based on its descendants. So basically, what we can do is search for every category that does not have any emoji that matches the search query. This means that the categories that do not have emojis that match the search query will hide themselves automatically. If we start typing right now, the bad one was bike, right? Yeah, their categories disappear and all we did was add this line. Now, granted, Has is not very performant and Has and Not are even more not performant. But given the fact that it works way faster than it did before, I think it's a good compromise. And just to be fair, let's see how it works with throttling before we judge it by seeing how it works on a low-end machine. And if I start typing, you see there's almost no delay at all. It works immediately even though we're throttling the CPU. And if we run the profiler, let's see if it actually does a full state update or not. We're seeing that the only thing that really updates is the search bar. So I think that's a pretty good solution for us. And I think that's pretty much it. Thank you so much for joining me today. If you want to see any more of my work, you should check out my website or Twitter account. Thank you so much. I hope you learned something new today. I had tons of fun.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Advanced Conference 2023React Advanced Conference 2023
19 min
Type-safe Styling for React Component Packages: Vanilla Extract CSS
Unlock the power of type-safe styling in React component packages with Vanilla Extract CSS. Learn how to effortlessly write scalable and maintainable styles, leveraging CSS-in-Typescript. Discover the seamless integration of Vanilla Extract CSS and take your React components to the next level.
React Summit 2023React Summit 2023
29 min
Moving on From Runtime Css-In-Js at Scale
In this talk, Siddharth shares the challenges his team faced with optimising runtime css in js (styled-components) for performance. At GitHub, there are about 4000 React components that contain styles, Siddharth dives into the reasons for rethinking styling architecture, the challenges faced and lessons learned by migrating a big application.
React Summit 2023React Summit 2023
9 min
Building Pixel-Perfect UI Components Using CSS Variables
CSS variables have become so sophisticated in recent years that they now enable us to do things that aren't possible with JavaScript. In this lightning talk, I'll explain how MUI's engineers are leveraging the power of CSS variables in our new React component library, Joy UI, to deliver components that automatically adapt their style and structure to the context in which they're rendered.

Workshops on related topic