cssinjs / cssinjs/jss

react-jss: Performance degradation with dynamic values

Open
#1,510 6 comments 2 reactions 0 assignees View on GitHub
perf
Dominant language
JavaScript
Stars
7.1k
Forks
386
PR merge metrics
No merged PRs in 30d

Description

Hi, I'm building components library with react-jss and when my stylesheets got large and complex I've started to experience performance degradation with dynamic values.

## Demo
- Go to [examples page](https://mantine.dev/pages/examples/#transactions-table)
- Enter any values at any description input (you need to type fast to see performance issue)
- See lag in input value

[Source code for demo component](https://github.com/mantinedev/mantine/blob/master/src/mantine-demos/src/TransactionsTable/TransactionsTable.tsx)

## Additional example
[Video showcase with another component](https://github.com/DavidWells/mantine-next-example/issues/1) with reproduction instructions

## Explanation
I think that this issue is caused by [this effect](https://github.com/cssinjs/jss/blob/master/packages/react-jss/src/createUseStyles.js#L67-L75). `useStyles` accept `data` object which is used as dependency in `useLayoutEffect`. If you use styles something like this (similar to docs examples):
```tsx
useStyles({ size: 'sm', color: 'red' });
```

Then in createUseStyles useEffect it will resolve in:

```tsx
useEffectOrLayoutEffect(() => {
if (sheet && dynamicRules && !isFirstMount.current) {
updateDynamicRules(data, sheet, dynamicRules);
}
}, [{ size: 'sm', color: 'red' }]);
```

Which means that `updateDynamicRules` function will be called at each render, this is not noticeable with small amount of styles but degrades performance significantly with larger stylesheets.

## My solution

I've created a drop in replacement for createUseStyles function that updates props object based on dependencies (it is specific to my project):

```tsx
function createMemoStyles(styles) {
const useStyles = createUseStyles(styles);

return function useMemoStyles(props) {
const dependencies =
typeof props === 'object' && props !== null
? Object.keys(props)
.filter((key) => key !== 'theme')
.map((key) => props[key])
: [];

if (typeof props === 'object' && 'theme' in props) {
dependencies.push(props.theme.colorScheme);
}

const stylesProps = useMemo(() => props, dependencies);
return useStyles(stylesProps);
};
}
```

This replacement resolves all performance issues for me.

## Possible solution
Calculate effect dependencies based on data in `useStyles`:

```tsx
function useStyles(data: any, dependencies: any[] = []) {
const dependencies =
typeof data === 'object' && data !== null
? Object.keys(data)
.filter((key) => key !== 'theme')
.map((key) => data[key])
: [data];

useEffectOrLayoutEffect(() => {
// We only need to update the rules on a subsequent update and not in the first mount
if (sheet && dynamicRules && !isFirstMount.current) {
updateDynamicRules(data, sheet, dynamicRules);
}
}, [dependencies, ...dependencies]);
}
```

This will solve issues with primitive values (`{ size: 'sm', color: 'red' }` from example above) and will allow to subscribe to effect with any extra values. Theme is filtered as it is always an object and will trigger effect on each render.

Contributor guide

Open the contributing guide

Research direction

Start with packages/react-jss/src/createUseStyles.js, especially the linked effect and updateDynamicRules call, then reproduce the lag by typing quickly in the linked TransactionsTable example. Compare updates for dynamic values across renders and use the linked examples to verify that the performance degradation is resolved without changing the intended style behavior.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.