react-jss: Performance degradation with dynamic values
- 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
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