mui / mui/material-ui

[system] Avoid iterating all styleOverrides keys per slot in createStyled

Open
#47,787 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

breaking change customization: theme package: material-ui performance scope: system
Dominant language
JavaScript
Stars
99.1k
Forks
32.5k
Avg merge
2d 17h
Merged PRs (30d)
106

Description

Summary

createStyled iterates over all styleOverrides keys for every slot, then overridesResolver picks only the relevant ones. This is unnecessary work that compounds as components grow in slots and override keys. It also caused a class of runtime errors (ownerState undefined) that required per-component workarounds.

Current behavior

In createStyled.js, styleThemeOverrides does this:

for (const slotKey in styleOverrides) {
  resolvedStyleOverrides[slotKey] = processStyle(
    props,
    styleOverrides[slotKey],
    ...
  );
}
return overridesResolver(props, resolvedStyleOverrides);

Every slot's styled component resolves all styleOverrides entries, then overridesResolver cherry-picks the needed ones.

Compounding cost — OutlinedInput example

OutlinedInput has 3 styled slots (Root, NotchedOutline, Input) and up to 15 styleOverrides keys (root, colorSecondary, focused, disabled, adornedStart, adornedEnd, error, sizeSmall, multiline, notchedOutline, input, inputSizeSmall, inputMultiline, inputAdornedStart, inputAdornedEnd).

With the current implementation, each render triggers:

  • Slot Root → iterates 15 keys, uses ~10
  • Slot NotchedOutline → iterates 15 keys, uses 1
  • Slot Input → iterates 15 keys, uses ~7

Total: 45 processStyle calls per render, ~27 of which are discarded. If styleOverrides values are callbacks, each discarded call still executes the function with the wrong slot's props.

For TextField (which composes InputBase + OutlinedInput + InputLabel + FormHelperText), the waste multiplies further.

Runtime errors from the same root cause

Because every slot calls every styleOverrides callback with its own props, slots that don't receive ownerState crash when callbacks depend on it:

  • #31982 — MuiOutlinedInput root callback called by NotchedOutline slot (no ownerState)
  • #43992 — Autocomplete styleOverrides crash on open
  • #36223 — Avatar ownerState undefined
  • #32799 — Alert ownerState undefined
  • #38707 — Tabs ownerState undefined

These were fixed with per-component workarounds (passing ownerState to internal slots). The systemic fix was deferred because overridesResolver accesses multiple keys from the resolved object — changing it would be breaking.

Proposed behavior

Each slot should only resolve its own styleOverrides entry:

// Instead of iterating all keys:
const resolved = processStyle(props, styleOverrides[slot], ...);
return resolved;

This requires changing overridesResolver to receive the resolved style directly instead of a map of all resolved styles. Components like InputBase that currently read multiple keys would need to be updated.

Context

Search keywords:

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in createStyled.js at styleThemeOverrides and follow overridesResolver, then inspect the existing TODO and InputBase's multiple-key access. Trace the affected styled slots and component workarounds; done means each slot resolves only its own styleOverrides entry without discarded callback calls, with all required resolver behavior updated.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, react
Domain
frontend, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.