question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

react-jss: Performance degradation with dynamic values

See original GitHub issue

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
  • 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

Additional example

Video showcase with another component with reproduction instructions

Explanation

I think that this issue is caused by this effect. useStyles accept data object which is used as dependency in useLayoutEffect. If you use styles something like this (similar to docs examples):

useStyles({ size: 'sm', color: 'red' });

Then in createUseStyles useEffect it will resolve in:

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):

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:

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.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:2
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

4reactions
sscaff1commented, Jan 12, 2022

Any updates on this issue? Or any recommended approaches?

1reaction
HenriBeckcommented, May 16, 2021

@rtivital, the code from your example has two issues:

The biggest one is that it only reacts to changes to the values of the keys but not the keys themselves. So while this assumption/statement can be made about your use/library, this isn’t feasible for public implementation.

func Component(props) {
  let data = { keyA: 'red' }
  if (props.disabled) {
    data = { keyB: 'red' }
  }

  const styles = useStyles(data)
}

While this code looks weird and is not great by any means, it still needs to be supported by us.
The code also only works for “flat” objects; e.g., if you have an object as the value in your data object, you would still have the same issue.


Secondly, the useStyles hook behaves the same way as any other React hook as the dependencies are not checked for deep equality but rather reference equality.
In my opinion, the proper usage in such a case should be to wrap the data object with a React.useMemo() on the caller side rather than the library.

You could then also think about writing a small utility hook that does this for you based on the dynamic object, e.g.:

const useMemoObject(obj) => React.useMemo(() => obj, [...Object.entries(obj)])
Read more comments on GitHub >

github_iconTop Results From Across the Web

Trying to implement Dynamic Values with React-JSS
I'm trying to utilize two dynamic values like this: import React from 'react' import {createUseStyles} from 'react-jss ...
Read more >
Debugging React performance issues with Why Did You Render
Debugging the React app. An easy solution to the performance issue would be to pass only the relevant value to Main instead of...
Read more >
High Performance Dynamic Styles - Medium
High Performance Dynamic Styles. Recently we released a new API for JSS, which is called “function values”. It delivers a 5–10× performance ......
Read more >
jss-plugin-rule-value-function | Yarn - Package Manager
[jss-plugin-rule-value-function] Add warning when using a function value inside a function rule (1285) · [react-jss][ts] Typescript support for createUseStyles ...
Read more >
Version 3.0.0 - Mantine
Mantine was migrated to emotion from react-jss and no longer supports ... no longer support themeOverride prop due to performance reasons ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found