Miłosz Orzeł

.net, js/ts, html/css, arduino, java... no rants or clickbaits.

The Daily Grind (Quick Tips on Miscellaneous Issues, Ep. 3)

Intro

Here's a third round of random issues and quick tips for working around them. Part one is here, Part two is here.

The issues:

 

Firestore index disappearing after deploy

If you create a query that uses couple of fields, Firestore would want you to add and index to make it fast (indexes for basic queries are added automatically). To make the setup easy, Firestore checks if query index is present when a query is invoked and if index is missing, an error is dumped into browser console. The error message contains a special link to index configuration page with all fields preconfigured. Nice!

So, I have deployed a new version of app (GitHub Actions job doing firebase deploy) that used compound query. As expected, the first query call failed and I've used the provided index configuration link. After a while index was added and then the query worked. But, after the app was deployed again, the query failed on a missing index...

The issue was that during deployment, the index configuration from firestore.indexes.json file was reapplied, overriding index added by link. To make the changes permanent I needed to first add the index with a link and then run firebase firestore:indexes command to update the indexes config. With the firestore.indexes.json refreshed and saved in the repo, all indexes were available after next deploy.

 

Firestore deploy failing because "index already exists"

Normally Firestore is smart enough not to complain if you redeploy a project that doesn't make any changes to indexes. Unfortunately, suddenly, it stated to just that during a GitHub Action that used firebase deploy:

Error: Request to https://firestore.googleapis.com/v1/projects/example/databases/(default)/collectionGroups/something/indexes had HTTP Error: 409, index already exists
[2025-07-22T07:30:23.592Z] Error Context: ***
  "body": ***
    "error": ***
      "code": 409,
      "message": "index already exists",
      "status": "ALREADY_EXISTS"
    ***
  ***,
  "response": ***
    "statusCode": 409
  ***
***

This issue on GitHub is about the same problem. Yes, it's from 2019 and marked as closed but notice that it has fresh (July 2025) comments mentioning that the problem still appears. Maybe Firestore team will address it. In the meantime removing the index manually through Firestore console and triggering redeployment fixed the issue (indexes form firestore.indexes.json were recreated).

 

Deployment failing on installing Firebase CLI

GitHub Action with a step that installs firebase-tools was working just fine, then suddenly builds stated to fail with such error:

-- Setting permissions on binary... /usr/local/bin/firebase
/usr/local/bin/firebase: line 1: Not: command not found
Something went wrong, firebase has not been installed.
Please file a bug with your system information on Github.
https://github.com/firebase/firebase-tools/
-- All done!
Error: Process completed with exit code 1.

It turned out that many people were affected by the same problem and an issue on the CLI repo was raised quicky. The problem is now fixed and Firebase team offered a brief explanation (artifacts for outdated version were built). This is not the first time such CI/CD pipeline mishap happened, a workaround is suggested here.

 

MUI X Date Picker stopped informing about invalid date

Recently a project I work on was migrated from @mui/material 6.4.6 and @mui/x-date-pickers: 7.21.0 to @mui/material: 7.1.1 and @mui/x-date-pickers: 8.5.2. The migration was mostly painless... Except for one regression: suddenly validation on date field stopped rejecting incomplete inputs. It used to happen that when user removed part of date, and placeholder started to be partially visible, the validation kicked in and user was not able to progress through questionnaire. After the migration, incomplete input was simply ignored and nothing prevented user from going forward (the field is not mandatory). This is quite dangerous, it's easy for people to accidently remove part of input with complex placeholders and save the form assuming that a correct value is provided, while it is silently dropped.

The problem happens because previous version provided Invalid Date as a value to onChange handler if input was incomplete, but recent version gives null in that case. 

There's an issue about it and I'm asking MUI team to restore some easy way to recognize incomplete input. There's hope, the issue appears on a milestone.

 

Warning about unnecessary React useEffect dependency on ref

I was doing a bit of layout simplification and wanted to get rid of MUI PaperTrapFocus wrapper on certain panel and replace it with a simple Box wrapper. It all went well except that the wrapper needed to be automatically focused when screen loaded (for the sake of some keyboard event handling). Initially I've added the focus by keeping a ref to the wrapper div (the Box) and focusing it in a useEffect that depended on the ref. In this specific case it worked, but it felt a bit dirty and made React linter complain with such message:

81:6 warning React Hook useEffect has an unnecessary dependency: 'wrapperRef.current'. Either exclude it or remove the dependency array. Mutable values like 'wrapperRef.current' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps

Fortunately there's a better way. In React, refs can point to functions too! All I needed was a function wrapped by useCallback hook that set the focus when wrapper element became available:

const wrapperFocusRef = useCallback((wrapper: HTMLDivElement) => {
 wrapper?.focus();
}, []);

<Box
  ref={wrapperFocusRef}
  sx={{ outline: 'none' }}
  tabIndex={-1}
  onKeyDown={handleKeyDown}
>

Check this GH comment and this older piece of docs for details.

 

Toast message unusable for copying

The app I work on uses React-Toastify, lib works well, but I noticed one annoying thing with our setup: it was difficult to select text from message. Trying to highlight part of text moved the toast on horizontal axis and reduced opacity. 

This happened becasue the draggable option was set to true in ToastContainerProps. With draggable option active, user can close the toast by sliding it across the screen. In my case this was not needed becasue messages are closable by a button.

If really necessary, the draggable could stay active on mobile devices (where swiping motion is natural) and be disabled on desktop (where selecting with mouse is natural). In Capacitor app, the decision can be made with isNativePlatform call. If disabling draggable is out of the question, some CSS tricks with .Toastify__toast and .Toastify__toast:hover classes could help (pointer-eventstransformopacity...).

Material UI 5 Box Performance

INTRO

I've been relying on Material UI Box components quite a lot, because doing so allows use of theme-aware sx property and common attributes such as display or gap. It makes code more consistent with other uses of MUI components.

The Box output is lightweight (it's just a div) but I was wondering how making plenty of these can impact performance, so I've build a test app to (ab)use the Box...

TL;DR: It's quite unlikely that Box vs div performance might become an issue in real application.

 

MUI v6

Just when I was writing this text, MUI team has released the v6.0.0 of Material UI. The release announcement blog post mentions runtime performance improvements and experimental availability of Pigment CSS (zero-runtime CSS-in-JS solution that will eventually replace use of Emotion and allow sx property on plain divs). 

I'm keeping this post focused on v5 (to be precise: v5.16.7 which was released less than 3 weeks ago). There are many projects that use v5 and will stay with it for a while. Plus it might be useful to compare the two major versions in the future...

 

STRESS TEST APP

I've made a small application (vite: 5.4.1, react: 18.3.1, @mui/material: 5.16.7, typescript: 5.5.3) to generate lots of squares with random colors (either by rendering plain divs elements or by using the MUI Box).

Live demo is here: https://morzel85.github.io/blog-post-mui-5-box-performance

The code is here: https://github.com/morzel85/blog-post-mui-5-box-performance

Box stress test app... Click to enlarge...

 

When the MAKE button is pressed, the app generates chosen amount of items. Use the CLEAR button to remove all items. Toggling between Plain div and MUI box options rerenders all the items. Divs are green, Boxes are purple. Simple.

Here's how the divs are created: 

import { memo } from 'react';

export const PlainDivs = memo(({ amount }: { amount: number }) =>
  Array.from({ length: amount }, (_, i) => (
    <div
      key={i}
      style={{
        background: 'darkgreen',
        opacity: Math.random(),
        margin: '1px',
        width: '15px',
        height: '15px'
      }}
    />
  ))
);

Yeah, inline styles are used even for static properties. These could be extracted out to single class but this is to make it closer to the Box/sx version.

This is how Box items are done:

import { memo } from 'react';
import { Box } from '@mui/material';

export const MuiBoxes = memo(({ amount }: { amount: number }) =>
  Array.from({ length: amount }, (_, i) => (
    <Box
      key={i}
      sx={{
        background: 'purple',
        opacity: Math.random(),
        margin: '1px',
        width: '15px',
        height: '15px'
      }}
    />
  ))
);

Notice that the random opacity rule is quite unfavorable for MUI/Emotion as it will generate a lot of different CSS classes that must be injected to the page at runtime! The generated CSS rule might look like this:

.css-s5s1br {
    background: purple;
    opacity: 0.846957;
    margin: 1px;
    width: 15px;
    height: 15px;
}

 

DESKTOP RESULTS

Here's a couple of results from my 7 years old PC with Intel Core i7-7700K and 16 GB RAM (MSI GTX 1080 Ti still going strong!) with Chrome 128 on Ubuntu 20.04.

  • For the default 500 items generating items feels practically instant for both Plain div and MUI Box. Same for 1K.
  • Let's go for 5K: divs are about 0.3s, Boxes about 0.4s.
  • How about 15K? Ok, now there's about a 1.2s of lag for div version and maybe 1.4s  for Box.
  • Well... 50K? 5,5s for divs and about 6,5s for Boxes.
    Quick sanity check: document.querySelectorAll('*').length -> 50056. It really created all those elements. Nice job React, nice job MUI! Aren't modern browsers a marvel of engineering? I remember the times when we had to worry about not putting too many JS validations on a form...

The above highly scientific results we collected by my eyeballs an stopwatch (time between pressing the MAKE button and page becoming responsive).

If you want something more precise here's performance trace for a 10K items: 

Plain div vs Box performance report... Click to enlarge...

 

Speed-wise there's not that much difference between Plain divs and Boxes version, although you can see that Box versions uses about 3.5x more memory. Sounds like a lot but the 10K of Boxes (with unnaturally large amount of unique classes) took about 30 MB.

Watch out for tests with too many items (especially if you open Elements tab), DevTools might choke a bit...

 

MOBILE RESULTS

Ok, how about a phone? This is how my 3+ years old, non-flagship, Samsung Galaxy A52 performs (Chrome 127 on Android 14):

  • 500 and 1K items: instant for both divs and Boxes. 
  • 5K: about 0.5s for divs and 0.9s for Boxes.
  • 15K: about 1.7s for divs and 3.3 for Boxes.
  • and finally the absurd 50K: about 15s for divs and 22s for Boxes (hope you never have to render this many elements on a desktop, let alone mobile)...

Speaking of DOM size, Lighthouse provides warnings and errors for excessive DOM size (as of this writing the thresholds are about 800 and 1400 elements respectively). It also reports DOM depth, it's a performance factor too (which my little app doesn't check, but the Box doesn't increase it). The largest sizes I've seen in practice was about 25K elements. When stuff like this happens is usually caused by a data grid with complex cell renderers and lack of virtualization (columns virtualization is important too).

 

BONUS INFO

When application is running in release mode (result of: npm run build), MUI/Emotion doesn't create individual style elements for each class.

When you click on <style> to see where the CSS rule is defined:

Finding style source... Click to enlarge...

 you will land on style element that appears empty:

Emotion style element... Click to enlarge...

 

This is a bit confusing, where are the classes? Emotion uses insertRule API which is very fast but the disadvantage is lack of DevTools support (check this GitHub issue and this answer in particular).

 

Update 2024-09-08:
I have a follow-up post that checks div performance without React: https://en.morzel.net/post/vanilla-div-performance