emotion-js / emotion

👩‍🎤 CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.4k stars 1.11k forks source link

[Question] Understanding `css` prop in react, performance & memoization #2987

Open fjcalzado opened 1 year ago

fjcalzado commented 1 year ago

Description: Thanks for this wonderful library, I love it! I am trying to better understand how css prop works under the hood for react in order to take the right decisions early and avoid performance bottleneck in complex applications.

As stated in the documentation, it is better to define static css outside the component in order to serialize just once and get a constant reference for whole component lifecycle, like this:

import React from 'react';
import { css } from '@emotion/react';

const fooCss = css({ backgroundColor: 'red' }); // fooCss is a constant reference

const Foo: React.FC = () => {
  return <div css={fooCss} />;
};

But what happens when we have a depencency with the theme?

First assumption: not-so-dynamic css. I mean, theme is unlikely to change frequently, so I assume we can still use css prop instead of style. Then, we have to pass a function right?

Whether we collocate the css using useTheme hook:

import React from 'react';
import { useTheme } from '@emotion/react';

const Foo: React.FC = () => {
  const theme = useTheme();
  return <div css={{ backgroundColor: theme.baseColor }} />;
};

or we pass an inline callback:

const Foo: React.FC = () => {
  return <div css={theme => ({ backgroundColor: theme.baseColor })} />;
};

or even if we extract that callback outside the component

import { css, Theme } from '@emotion/react';

const fooCss = (theme: Theme) => css({ backgroundColor: theme.baseColor });

const Foo: React.FC = () => {
  return <div css={fooCss} />;
};

none of the options will prevent css from being serialized on every render of the Foo component, is that correct? Are all the 3 approaches the same in terms of performance?

Will a memoization based on theme add any benefit or is overkill or problematic with emotion internals ? A simple utility like this might do the trick:

import React from 'react';
import { FunctionInterpolation, Theme } from '@emotion/react';

const cssMemo = (cssCallback: FunctionInterpolation<Theme>) => theme =>
  React.useMemo(() => cssCallback(theme), [theme]);

const Foo: React.FC = () => {
  return <div css={cssMemo(theme => ({ backgroundColor: theme.baseColor }))} />;
};

but then I wonder why emotion does not memoize these callbacks by default?

Environment information:

@emotion/react v11.10.5 @emotion/babel-plugin v11.10.5 react v17.0.2 react-dom 17.0.2

gevhambarzumyan93 commented 1 year ago

this is what you get if do as written

You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop).