garronej / tss-react

✨ Dynamic CSS-in-TS solution, based on Emotion
https://tss-react.dev
MIT License
659 stars 37 forks source link

[Support] MakeStyles crashes with "Can't assign to property" error #93

Closed thehig closed 2 years ago

thehig commented 2 years ago

I've recently switched from @mui/styles to react-tss and I'm experiencing an error:

Uncaught TypeError: can't assign to property "label" on "ltr": not an object

Tracking the error down I'm lead to the makeStyles.js file, line ~45

  const cssObject = cssObjectByRuleName[ruleName];
  if (!cssObject.label) {
      cssObject.label = `${name !== undefined ? `${name}-` : ""}${ruleName}`;
  }

In this error case, the ruleName is direction resulting in a cssObject of the string ltr. Attempting to write to cssObject.label causes the error above.

I've checked my codebase and I'm not modifying the direction anywhere, so I don't really understand why this is happening and could really use some assistance.

Thanks

garronej commented 2 years ago

Hi @thehig,
I'm assuming you are using TSS in a JavaScript project, can you confirm?
I assume you might be using TSS incorrectly somewhere.
Did you use the codemod to migrate to TSS or did you do it by hand?
Maybe you are calling makeStyles this way:

const useStyles = makeStyles(theme => ({
    root: {}
});

instead of this way: (which is the correct way)

const useStyles = makeStyles()(theme => ({
    root: {}
});
thehig commented 2 years ago

I'm assuming you are using TSS in a JavaScript project, can you confirm?

Yep

Did you use the codemod to migrate to TSS or did you do it by hand?

I did use the codemod, yes, but it didn't hit everything.

I just checked my code and I only use the makeStyles in 4 places, always with the "correct" structure from the post above.

I went through my codebase and removed all usages of the makeStyles function but the error persists. Could the same kind of structure/invocation bug come from withStyles usage?

garronej commented 2 years ago

Yes, withStyles uses makeStyles internally.

Maybe rename your .js files into .tsx files just to let TypeScript tell you what's wrong.

thehig commented 2 years ago

Maybe rename your .js files into .tsx files just to let TypeScript tell you what's wrong.

Thats not the easiest solution in such a large codebase. The thing that bugs me is I don't set direction anywhere so it must be coming from the base theme

garronej commented 2 years ago

Not everywhere, just on some files, momentarily to see if typescript can detect an error.

Do you use the theme <ThemeProvider /> as described in the doc? https://docs.tss-react.dev/readme-1

If I could see the code I would probably be able to spot the error quickly

garronej commented 2 years ago

Just in cas, It may help, https://docs.tss-react.dev/page-1/withstyles

thehig commented 2 years ago

Do you use the theme as described in the doc? https://docs.tss-react.dev/readme-1

Yep


So, I create my Theme based on some theme object in state, some theme object located in my config, and createTheme func.

import { createSelector } from "reselect";
import { createTheme } from "@mui/material/styles";
import config from "config/index";

// Get the tenant theme configuration and *MERGE WITH* config if present
const selectTheme = createSelector(
  (state) => selectTenant(state).theme,
  (theme = {}) => {
    // Make sure that the theme we're initializing is not the same reference in state as the tenant response
    const tenantTheme = JSON.parse(JSON.stringify(theme));
    console.log("Tenant Theme", tenantTheme);
    let themes = tenantTheme;

    if (config.has("theme")) {
      const configTheme = config.get("theme");
      console.log("Config Theme", configTheme);

      themes = merge(tenantTheme, configTheme);
    }

    const newTheme = createTheme(themes);
    console.log("Completed Theme", newTheme);
    return newTheme;
  }
);
Tenant Theme ```json { "palette": { "primary": { "main": "#c6a861", "contrastText": "#ffffff" }, "background": { "menu": "#565462" }, "charts": { "primary": [ "#0B283D", "#14477C", "#0368B1", "#2C9FE5", "#7DCEFF", "#A6E1FF", "#AEDEFF" ], "secondary": ["#E55C00", "#FE7A00", "#E0E0E0"] }, "common": { "blue-grey": "#565462", "grey-1": "#989898", "lawn-green": "#4bae08", "cherry-red-two": "#e2031e" } }, "shadows": [ "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none" ], "typography": { "fontSize": 14, "htmlFontSize": 16, "fontWeightRegular": 400, "fontWeightLight": 600, "fontWeightMedium": 800, "useNextVariants": true, "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif", "h1": { "fontSize": "2.75rem", "lineHeight": "1.18182em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h2": { "fontSize": "2.125rem", "lineHeight": "1.17647em", "letterSpacing": "-0.3px", "fontWeight": 800, "color": "#565462" }, "h3": { "fontSize": "1.5rem", "lineHeight": "1.33333em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h4": { "fontSize": "1.25rem", "lineHeight": "1.4em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h5": { "fontSize": "1.0625rem", "lineHeight": "1.41176em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h6": {}, "subtitle1": { "fontSize": "1.0625rem", "lineHeight": "1.41176em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462" }, "subtitle2": {}, "body1": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462" }, "body2": { "fontSize": "0.875rem", "lineHeight": "1.57143em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462" }, "button": { "fontSize": "0.875rem", "lineHeight": "1.71429em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462", "textTransform": "none" }, "caption": { "fontSize": "0.6875rem", "lineHeight": "1.45455em", "letterSpacing": "0px", "fontWeight": 600, "color": "#989898" }, "overline": {} }, "components": { "MuiTableCell": { "styleOverrides": { "head": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": 600, "color": "#565462" }, "body": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": "inherit", "color": "#565462" } } }, "MuiPaper": { "styleOverrides": { "root": { "borderStyle": "solid", "borderWidth": "1px", "borderColor": "#CACACA" } } }, "MuiTooltip": { "styleOverrides": { "tooltip": { "fontSize": "0.9375rem", "fontWeight": 400 } } } } } ```
Config Theme ```javascript { "shadows": [ "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none" ], "typography": { "fontSize": 14, "htmlFontSize": 16, "fontWeightRegular": 400, "fontWeightLight": 600, "fontWeightMedium": 800, "useNextVariants": true, "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif", "h1": { "fontSize": "2.75rem", "lineHeight": "1.18182em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h2": { "fontSize": "2.125rem", "lineHeight": "1.17647em", "letterSpacing": "-0.3px", "fontWeight": 800, "color": "#565462" }, "h3": { "fontSize": "1.5rem", "lineHeight": "1.33333em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h4": { "fontSize": "1.25rem", "lineHeight": "1.4em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h5": { "fontSize": "1.0625rem", "lineHeight": "1.41176em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462" }, "h6": {}, "subtitle1": { "fontSize": "1.0625rem", "lineHeight": "1.41176em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462" }, "subtitle2": {}, "body1": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462" }, "body2": { "fontSize": "0.875rem", "lineHeight": "1.57143em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462" }, "button": { "fontSize": "0.875rem", "lineHeight": "1.71429em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462", "textTransform": "none" }, "caption": { "fontSize": "0.6875rem", "lineHeight": "1.45455em", "letterSpacing": "0px", "fontWeight": 600, "color": "#989898" }, "overline": {} }, "palette": { "common": { "blue-grey": "#565462", "grey-1": "#989898", "lawn-green": "#4bae08", "cherry-red-two": "#e2031e" } }, "components": { "MuiTableCell": { "styleOverrides": { "head": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": 600, "color": "#565462" }, "body": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": "inherit", "color": "#565462" } } }, "MuiPaper": { "styleOverrides": { "root": { "borderStyle": "solid", "borderWidth": "1px", "borderColor": "#CACACA" } } }, "MuiTooltip": { "styleOverrides": { "tooltip": { "fontSize": "0.9375rem", "fontWeight": 400 } } } } } ```
Completed Theme ```javascript { "breakpoints": { "keys": [ "xs", "sm", "md", "lg", "xl" ], "values": { "xs": 0, "sm": 600, "md": 900, "lg": 1200, "xl": 1536 }, "unit": "px", "label": "breakpoints" }, "direction": "ltr", "components": { "MuiTableCell": { "styleOverrides": { "head": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": 600, "color": "#565462" }, "body": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": "inherit", "color": "#565462" } } }, "MuiPaper": { "styleOverrides": { "root": { "borderStyle": "solid", "borderWidth": "1px", "borderColor": "#CACACA" } } }, "MuiTooltip": { "styleOverrides": { "tooltip": { "fontSize": "0.9375rem", "fontWeight": 400 } } } }, "palette": { "mode": "light", "primary": { "300": "rgba(0, 0, 0, 0.87)", "main": "#c6a861", "contrastText": "#ffffff", "light": "rgb(209, 185, 128)", "dark": "rgb(138, 117, 67)" }, "background": { "menu": "#565462", "paper": "#fff", "default": "#fff" }, "charts": { "primary": [ "#0B283D", "#14477C", "#0368B1", "#2C9FE5", "#7DCEFF", "#A6E1FF", "#AEDEFF" ], "secondary": [ "#E55C00", "#FE7A00", "#E0E0E0" ] }, "common": { "blue-grey": "#565462", "grey-1": "#989898", "lawn-green": "#4bae08", "cherry-red-two": "#e2031e", "black": "#000", "white": "#fff" }, "secondary": { "main": "#9c27b0", "light": "#ba68c8", "dark": "#7b1fa2", "contrastText": "#fff" }, "error": { "main": "#d32f2f", "light": "#ef5350", "dark": "#c62828", "contrastText": "#fff" }, "warning": { "main": "#ed6c02", "light": "#ff9800", "dark": "#e65100", "contrastText": "#fff" }, "info": { "main": "#0288d1", "light": "#03a9f4", "dark": "#01579b", "contrastText": "#fff" }, "success": { "main": "#2e7d32", "light": "#4caf50", "dark": "#1b5e20", "contrastText": "#fff" }, "grey": { "50": "#fafafa", "100": "#f5f5f5", "200": "#eeeeee", "300": "#e0e0e0", "400": "#bdbdbd", "500": "#9e9e9e", "600": "#757575", "700": "#616161", "800": "#424242", "900": "#212121", "A100": "#f5f5f5", "A200": "#eeeeee", "A400": "#bdbdbd", "A700": "#616161" }, "contrastThreshold": 3, "tonalOffset": 0.2, "text": { "primary": "rgba(0, 0, 0, 0.87)", "secondary": "rgba(0, 0, 0, 0.6)", "disabled": "rgba(0, 0, 0, 0.38)", "primaryChannel": "0 0 0", "secondaryChannel": "0 0 0" }, "divider": "rgba(0, 0, 0, 0.12)", "action": { "active": "rgba(0, 0, 0, 0.54)", "hover": "rgba(0, 0, 0, 0.04)", "hoverOpacity": 0.04, "selected": "rgba(0, 0, 0, 0.08)", "selectedOpacity": 0.08, "disabled": "rgba(0, 0, 0, 0.26)", "disabledBackground": "rgba(0, 0, 0, 0.12)", "disabledOpacity": 0.38, "focus": "rgba(0, 0, 0, 0.12)", "focusOpacity": 0.12, "activatedOpacity": 0.12, "activeChannel": "0 0 0", "selectedChannel": "0 0 0" } }, "shape": { "borderRadius": 4 }, "shadows": [ "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none" ], "typography": { "fontSize": 14, "htmlFontSize": 16, "fontWeightRegular": 400, "fontWeightLight": 600, "fontWeightMedium": 800, "useNextVariants": true, "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif", "h1": { "fontSize": "2.75rem", "lineHeight": "1.18182em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "h2": { "fontSize": "2.125rem", "lineHeight": "1.17647em", "letterSpacing": "-0.3px", "fontWeight": 800, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "h3": { "fontSize": "1.5rem", "lineHeight": "1.33333em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "h4": { "fontSize": "1.25rem", "lineHeight": "1.4em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "h5": { "fontSize": "1.0625rem", "lineHeight": "1.41176em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "h6": { "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif", "fontWeight": 800, "fontSize": "1.25rem", "lineHeight": 1.6 }, "subtitle1": { "fontSize": "1.0625rem", "lineHeight": "1.41176em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "subtitle2": { "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif", "fontWeight": 800, "fontSize": "0.875rem", "lineHeight": 1.57 }, "body1": { "fontSize": "0.9375rem", "lineHeight": "1.6em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "body2": { "fontSize": "0.875rem", "lineHeight": "1.57143em", "letterSpacing": "0px", "fontWeight": 400, "color": "#565462", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "button": { "fontSize": "0.875rem", "lineHeight": "1.71429em", "letterSpacing": "0px", "fontWeight": 800, "color": "#565462", "textTransform": "none", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "caption": { "fontSize": "0.6875rem", "lineHeight": "1.45455em", "letterSpacing": "0px", "fontWeight": 600, "color": "#989898", "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif" }, "overline": { "fontFamily": "\"Open Sans\", \"Roboto\", \"Helvetica\", \"Arial\", sans-serif", "fontWeight": 400, "fontSize": "0.75rem", "lineHeight": 2.66, "textTransform": "uppercase" }, "fontWeightBold": 700 }, "mixins": { "toolbar": { "minHeight": 56, "@media (min-width:0px)": { "@media (orientation: landscape)": { "minHeight": 48 } }, "@media (min-width:600px)": { "minHeight": 64 } } }, "transitions": { "easing": { "easeInOut": "cubic-bezier(0.4, 0, 0.2, 1)", "easeOut": "cubic-bezier(0.0, 0, 0.2, 1)", "easeIn": "cubic-bezier(0.4, 0, 1, 1)", "sharp": "cubic-bezier(0.4, 0, 0.6, 1)" }, "duration": { "shortest": 150, "shorter": 200, "short": 250, "standard": 300, "complex": 375, "enteringScreen": 225, "leavingScreen": 195 } }, "zIndex": { "mobileStepper": 1000, "fab": 1050, "speedDial": 1050, "appBar": 1100, "drawer": 1200, "modal": 1300, "snackbar": 1400, "tooltip": 1500 } } ```

And then the component

import React, { PureComponent } from "react";
import PropTypes from "prop-types";

import { ThemeProvider } from "@mui/material/styles";

import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";

export const muiCache = createCache({
  key: "mui",
  prepend: true,
});

class TenantProviders extends PureComponent {
  //... some other code stuff
  render = () => {
    const {
      onError,
      props: { theme, children },
    } = this;

    // Wrapped in some other providers for INTL and Localization, ommitted for clarity
    return (
      <CacheProvider value={muiCache}>
        <ThemeProvider theme={theme}>{children}</ThemeProvider>
      </CacheProvider>
    );
  };
}

// Redux mapStateToProps invokes the `selectTheme` selector above
thehig commented 2 years ago

I tried to make a sample to demonstrate the issue I'm having, but it doesn't have the same error.

https://codesandbox.io/s/bold-bird-v2t5pc?file=/src/Component.jsx

thehig commented 2 years ago

I think I figured it out, I had a botched attempt at replicating the withTheme behavior from older MUI. Any tips on how to implement this?

garronej commented 2 years ago

Glad you figured it out.

This would be withTheme:

import { useStyles } from "tss-react/mui";

function withTheme(Component){

   function ComponentWithTheme(props){

        const { theme }= useStyles();

        return <Component {...props} theme={theme} />;

   }

   ComponentWithTheme.name = `${Component.name}WithTheme`;

   return ComponentWithTheme;

}
thehig commented 2 years ago

Yup that worked! Thanks, and sorry for the bother

garronej commented 2 years ago

No problem, happy to help