garronej / tss-react Goto Github PK
View Code? Open in Web Editor NEW✨ Dynamic CSS-in-TS solution, based on Emotion
Home Page: https://tss-react.dev
License: MIT License
✨ Dynamic CSS-in-TS solution, based on Emotion
Home Page: https://tss-react.dev
License: MIT License
Hello,
when I created this PR #54 we were still using MUIv4. We are using overrides with classes
prop and withStyles
of MUI components a lot and since we started migrating to MUIv5, I've noticed something strange.
I've create a demo here:
I followed the recommended steps from both tss-react
and MUI projects to eliminate issues with the setup.
The problem in a nutshell is the following, when you have a component with sub-elements and you'd like to override default styles (let's say color or background color), one would expect to use what was possible in MUIv4:
const StyledChip = withStyles(Chip, (theme) => ({
root: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
},
deleteIcon: {
color: theme.palette.primary.contrastText,
'&:hover': {
color: `${theme.palette.primary.contrastText}6`,
},
},
}));
But it doesn't work because of CSS selector specificity:
The only solution I found is with:
const StyledChip = withStyles(Chip, (theme, _props, classes) => ({
root: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
[`& .${classes.deleteIcon}`]: {
color: theme.palette.primary.contrastText,
'&:hover': {
color: `${theme.palette.primary.contrastText}6`,
},
},
},
}));
Unfortunately it requires a lot of refactoring in our code and it's also quite hard to reason about, you need to know that you are fighting with specificity here (which I assumed that emotion
-based solution should help to avoid).
I'm not 100% sure if it's a problem in tss-react
itself or if it's more about how MUIv5 handles classes
props.
Hi, thank you for the awesome work you are providing here.
I'm using tss to replace makestyles on a mui migration.
I have a component library that i use in an application i am currently migrating to mui v5.
Both of them are using tss and i am struggling with style injection, the same way i struggled with the makeStyle back to mui v4.
here in my application:
The first tss class is comming from my library and should be placed below the second.
My classes are declared with clsx
like so:
className={clsx(
className,
'AruiAppBar-root',
defaultStyles.classes.root
)}
For jss i quickly found the index
option provided on a makestyle that can tell the injection priority.
Is there anything similar in tss?
I saw in this ticket #56 that the placement of the classes does a difference in the cx
function.
Is it the same foc clsx? I remember on mui they specified that the placement does not impact the injection of the styles.
Do i have to use the function cx
insted of clsx
?
Problem: i use composition as you proposed in docs, but there is a problem when i use it with classes
props from MUI.
Let's i have styles object tableRow
, i need it class name to use in composition, i'm doing css(tableRow)
and it returns tss-react-1yphcmd
i then use classes.tableRow
in component code like this classes={{ root: classes.tableRow }}
.
Then in browser i see this element has class tss-react-1yphcmd-MuiTableRow-root
, i.e. something added -MuiTableRow-root
string. What's the problem? I see easy workaround here but i really would prefer to simply use css(tableRow)
.
Following up on our discussion started in mui/material-ui#26571 about how we can make tss-react
compatible as a replacement for the makeStyles
API for v5. I've done some initial testing. Here are some thoughts:
Have you tried to configure nextjs to work with tss-react
? It uses @emotion/css so I believe some extra steps would be required. This would be the first requirement so that we could use it together with material-ui. Our docs (the Material-UI's) can be a good candidate for testing this out.
Here are some suggestions regarding the API itself:
I wouldn't return named options, if there is only one option returned. For example:
-const { createUseClassNames } = createUseClassNamesFactory({ useTheme }); // createUseClassNames is the only option
+const makeStyles = createUseClassNamesFactory({ useTheme });
-const { useClassNames } = createUseClassNames()(
+const useStyles = makeStyles()(
(theme)=> ({
root: {
color: theme.palette.primary.main,
},
})
);
With this, we can actually make the API closer to the v4 makeStyles
API. We could overcome this by adding adapters, but I will leave it to you.
Question: Is it really necessary to invoke function createUseClassNames()
and then propagate the input to the next function call? What are the arguments required there? Can it be omitted?
I am open to help and move this forward :)
We've been making styles extendable by something like this:
export type UseStyles<T extends (...args: never[]) => unknown> = {
classes?: Partial<ReturnType<T>['classes']>
}
const useStyles = makeStyles({ name: 'FixedFab' })((theme: Theme) => ({
root: {
width: '100%',
},
}))
type ComponentProps = UseStyles<typeof useStyles>
function Component(props: ComponentProps) {
const { classes } = useStyles(props)
return <div className={classes.root}>asdf</div>
}
function ExtendingComponent() {
return <Component classes={{ root: 'foo' }} />
}
It currently isn't possible to pass the classes prop, might this be something that can be added?
Hi there!
I'm running into an issue where the order of css and tss styles are sorted in the wrong way in the head for a short period of time..
Video of the problem: https://d.pr/v/SXxXJq
You can see it happening from the 15 second mark in the video, you see the tss-
below the css-
styles for a short while
It seems to be due to a reordering in the header which is happening teporarily:
CSS styles get applied below the tss styles:
CSS styles are now moved to the top:
I'm using the following code:
// eslint-disable-next-line @next/next/no-document-import-in-page
import type Document from 'next/document'
import { withEmotionCache } from 'tss-react/nextJs'
import { emotionCache } from './tssReact'
export function documentWithEmotion(Doc: typeof Document) {
return withEmotionCache({ Document: Doc, getCaches: () => [emotionCache()] })
}
import createCache from '@emotion/cache'
import type { EmotionCache } from '@emotion/cache'
import { CacheProvider } from '@emotion/react'
import { getTssDefaultEmotionCache, TssCacheProvider } from 'tss-react'
let cache: EmotionCache | undefined
/** Creates a single instance of the `@emotion/cache` that is shared between server and client */
export function emotionCache() {
if (!cache) cache = createCache({ key: 'mui', prepend: true })
return cache
}
/** Provider that is supposed to be used in your `pages/_app.tsx` */
export function EmotionProvider({ children }: { children: React.ReactNode }) {
return (
<CacheProvider value={emotionCache()}>
<TssCacheProvider value={getTssDefaultEmotionCache()}>{children}</TssCacheProvider>
</CacheProvider>
)
}
I've tried without the TssCacheProvider first, but that doesn't seem to be making a difference.
Ideas: After googling a bit we might be able to use this option to make this more stable?
emotion-js/emotion#2037 (comment)
(In general it bothers me a bit that it even recreates styles in the DOM, because that means that the browser must reevaluate the styles, but maybe that goes away if the locations are exactly the same..)
(I promise I'm almost done bothering you with all these issues 😗)
Hi @garronej
First of all - thank you so much for your work - I was really worried about the direction of MUI's styling for v5.
Trying to implement tss-react as my team has moved to migrated to mui v5 but having a lot of trouble. I've been reading your documentation, the mui migration guide, issues, everything I can, but I've yet to successfully apply tss-react properly. I believe I have most things set up properly, as the styles generated are correct, but the ultimate problem is order/specificity. MUI v5 styles are the same specificity of tss-react generated ones and come after, therefore they always override what tss generates on components.
My relevant configuration:
makeStyles.ts
import { createMakeStyles } from 'tss-react';
import theme from '$src/theme';
function useTheme() { return theme; }
export const { makeStyles, useStyles } = createMakeStyles({ useTheme });
index.tsx
import { CacheProvider } from '@emotion/react';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { render } from 'react-dom';
import createCache from 'tss-react/@emotion/cache';
import Root from '$components/Root';
import theme from '$src/theme';
export const muiCache = createCache({
key: 'mui', // all material ui classes start with 'css' instead of 'mui' even with this here
prepend: true,
});
render(
<CacheProvider value={muiCache}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Root />
</ThemeProvider>
</CacheProvider>,
document.getElementById('root')
);
An example of how I'm using it in a component:
Banner.tsx
import { Typography } from '@mui/material';
import React from 'react';
import useStyles from './styles';
export default function Banner(): JSX.Element {
const { classes } = useStyles();
return (
// no problems here
<div className={classes.banner}>
// two classes here, ".css-XX-MuiTypography-root" and ".tss-XX" but the former wins by order
<Typography className={classes.text}>
Banner
</Typography>
</div>
);
}
styles.ts
import { makeStyles } from '$src/makeStyles.ts';
export default makeStyles()((theme) => ({
banner: {
// styles here applied to a div, everything good
},
text: {
// styles here conflict with mui styles coming from ".css-XX-MuiTypography-root"
}
});
I don't know what I'm missing. Unfortunately it's a bit difficult for me to make a workable copy as my code is on another system. I will do my best to explain more if needed.
Hello, I am trying to figure out how to select nested children by class name using this library. Below is an example of how we did it with JSS:
el1: {
'&:hover $el2': {
display: 'block',
},
},
el2: {
display: 'none'
},
Is this currently supported? The docs say:
tss-react unlike jss-react doesn't support the $ syntax, but you'll see. It isn't needed.
But don't clarify how we would do the above.
Hi,
I'm migrating a project from using jss towards tss-react.
We use MUI5 and after migrating to tss-react i see some classes are changed in order.
I already checked some other topics on this behavior.
What i already did:
As an example:
On a mui typography the -root styling of mui is applied in a higher order then the classname which we provides to item.
This results in not applying our classname styling to it.
First of all I'd like to express my gratitude on the great work done on this, it's made my migration to MUI 5 actually possible. Thanks!
Second, I know that a lot of current libraries have dropped ie11 support, and even MUIv5 states that there is only partial support for it.
I just wanted to report the issue in the hopes that there is an easy solution where we can allow our users to have some sort of experience with ie11, even if not ideal, as opposed to have to show them an error page and not being able to style anything.
Thanks
Let's assume we have ComponentA and ComponentB in different files.
// file1.tsx
const useStyles = makeStyles()(({ palette, breakpoints }) => ({
extra: {
color: "red"
}
}));
const ComponentA = () => {
const { classes, cx } = useStyles();
return <ComponentB extraClasses={classes.extra} />;
}
// file2.tsx
const useStyles = makeStyles()(({ palette, breakpoints }) => ({
root: {
color: "blue",
[breakpoints.up("sm")]: {
color: "yellow"
}
}
}));
export const ComponentB = ({ extraClasses }: { extraClasses?: string }) => {
const { classes, cx } = useStyles();
return <div className={clsx(classes.root, extraClasses)}>Hi</div>;
};
Ideally when using JSS, the extraClass in ComponentA will override the root class in ComponentB which can produce the right result on screen, which is that the text should be in "red" color.
Problems:
If using clsx to merge classes, then two classes will be generated but in wrong sequence. The root class in ComponentB will always override extraClass in componentA and display "blue" color which is wrong.
If using cx to merge classes, then one merged class will be generated in right sequence, but if there's any breakpoint related css in ComponentB, then it will not work well because the css priority is always breakpoint first, so it will display "yellow" color which is also wrong.
Thanks for building this amazing package and this is the only issue we have so far.
Hope you can provide a fix or let us know if there's any workaround. Our project is huge so finding them and changing them one by one is kind of impossible..
Cheers
CJ
Could you implement withStyles
in tss-react
too? It's convenient when just want to overwrite the style of a Mui Component.
const StyledLinearProgress = withStyles(LinearProgress)({
root: {},
// other LinearProgressProps.classes properties...
})
Originally posted by @Jack-Works in mui/material-ui#26571 (comment)
Hi, while building our project we get these errors from Yarn 3 with PnP.
./.yarn/__virtual__/tss-react-virtual-f99a3cd5a8/0/cache/tss-react-npm-2.0.2-7ea9c3ac36-21cc281768.zip/node_modules/tss-react/cssAndCx.js
Module not found: tss-react tried to access @emotion/serialize, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.
and
./.yarn/__virtual__/tss-react-virtual-f99a3cd5a8/0/cache/tss-react-npm-2.0.2-7ea9c3ac36-21cc281768.zip/node_modules/tss-react/cssAndCx.js
Module not found: tss-react tried to access @emotion/utils, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.
While this can be fixed via .yarnrc.yml, I would appreciate it if you could add these as dependencies to your package.
Hello!
I discovered an issue in my app (Next JS with Material UI v5 and "next-dark-mode"), and hoped you might be able to help :)
Determining the theme based on a cookie seems to generate two sets of stylesheets on the server. This breaks the theming on the client.
https://codesandbox.io/s/vigorous-noether-5xxr6
Switching themes twice "fixes" the theme to have the right colors, but only until the next page visit / next refresh.
It looks like the cookie seems to work incorrectly when theme styles are generated on the server side.
After upgrading to MUI 5 last weekend, I noticed that the footer of my app always had the links in the wrong color for some reason. So I tried to investigate.
I saw that I seemed to be getting two sets of styles on the client, as can be seen in the screenshot below:
These conflicted with each other in somewhat unpredictable ways. In my case the result was that 99% of the app seemed to look fine, except for the footer. In the CodeSandbox above, it seemed to affect the body background color as well.
I'm using the "next-dark-mode" library, which uses a cookie to store the user's dark mode preference. This cookie is supposed to let my Theme
component determine the right theme during server-side rendering. In my app all pages are currently server-side rendered (i.e. no static pages), but the theming bug is still present.
In the gif above you can see that the theme seems to be generated correctly when the user clicks a button to switch modes, but breaks when the user comes back to the page (e.g. after refreshing).
If I remove getInitialProps
from my _document.tsx
file, then this bug does not occur.
.
I'm hoping that this is just a user-error on my part. However, I didn't have this issue in MUI 4 (back then I used ServerStyleSheets
from MUI styles and merged them in getInitialProps
in _document.tsx
in much the same way as now.)
To be honest I have no idea what's really happening under the hood here. I'm a bit new to Next JS and certainly very new to emotion (e.g. I have no idea whatsoever how it's caching works). But before I spend many hours debugging and diving down the info rabbit hole of how server-side rendered styles work, I thought I'd try my luck asking for help here first 😄
While I'm here, allow me to say that I really love this library. It made my MUI 5 migration very smooth and easy, I have no idea how many hours it would've taken if I had to refactor everything to styled components.
Also, I really love how the results of functions return objects. It makes it so easy to just autocomplete the results of everything, so I never make typos and I never have to worry about naming things. And of course being a big TS fan I love that everything could be kept type-safe 😄 So yeah, even if I need to take this issue to another library, just wanted to say thanks a lot for your hard work! 😄
If I'm not mistaken, in this line, the code should not be
.map(classNameOrCSSObject =>
typeof classNameOrCSSObject === "string"
? className
: css(classNameOrCSSObject),
)
but instead
.map(classNameOrCSSObject =>
typeof classNameOrCSSObject === "string"
? classNameOrCSSObject
: css(classNameOrCSSObject),
)
Module not found
I get the following error when compiling my project in the latest version.
Module not found: Error: Can't resolve 'tsafe/typeGuard' in '/node_modules/tss-react'
Following this discussion I am probing the community's opinion on the following API change:
-const { useStyles } = makeStyles()(...);
+const useStyles = makeStyles()(...);
I'm having issues using tss-react
in a single spa environment. In my root app, I am able to get the UMD version of MUI
and emotion
(not been able to find an umd/systemjs version of this package or create one successfully):
"@mui/material": "https://unpkg.com/@mui/[email protected]/umd/material-ui.production.min.js",
"@emotion/react": "https://cdn.jsdelivr.net/npm/@emotion/[email protected]/dist/emotion-react.umd.min.js",
"@emotion/styled": "https://cdn.jsdelivr.net/npm/@emotion/[email protected]/dist/emotion-styled.umd.min.js",
And they are being externalised by webpack in the sub app I am trying to use tss-react
, In this app I have created a cache as the docs showed:
export const muiCache = createCache({
'key': 'mui',
'prepend': true,
});
// region Component
const Theme: FunctionComponent<IThemeProps> = ({
children,
style
}) => {
// handlers
const theme = React.useMemo(() => {
switch (style) {
case Themes.Default:
default:
return DefaultTheme;
}
}, [style]);
// render
return (
<CacheProvider value={muiCache}>
<MuiThemeProvider theme={theme}>
{children}
</MuiThemeProvider>
</CacheProvider>
);
};
But MUI
css always takes precedence over tss-react
created styles and cannot seem to get it to work. Is there a way to get this to work?
Hi there,
I'm currently trying out tss-react
and noticed a roadblock, which I'm not sure how to solve it: I'm unable to change the color of the deleteIcon
of a Chip
since the original styles take precedence. I also followed the steps of the README, see here for a repro: https://stackblitz.com/edit/tss-react-aupnsq?file=Hello.tsx
As you can see, overriding the color in the theme works, but individually styling it with withStyles
creates the wrong class order.
Am I missing something obvious? Do I maybe somehow have to specify MuiButtonBase-root-MuiChip-root
so it it's on the same level?
We have a Next.js application that we are in the midst of upgrading from MUI v4 to MUI v5 with react-tss. Part of our theme file is as follows:
MuiLink: {
styleOverrides: {
root: {
color: '#0068b5',
textDecoration: 'none',
},
underlineNone: {
color: '#f9f9f6',
},
},
defaultProps: {
underline: 'hover',
},
},
In MUI v4, this color was correctly applied. However, in MUI v5 with react-tss, our link styles look like this:
It appears that instead of replacing the original color
CSS property the new color is simply being added as an additional property. And unfortunately, probably based on the order styles are applied, the wrong color is taking precedence over the desired color. Any ideas here?
I am currently using a project with Material UI 4 and am in the process of changing to MUI 5. I had used a lot of makeStyles and would like to still use this way of styling. I saw this repo and thought this would be a great way to still use makeStyles, but then saw that you guys specifically made this for TS and not JS. So I am asking if you guys support JS and if you do, how can I then add it to my project? Maybe there could be a specific guide with everything you need to do to convert your MUI 5 project to use this library?
Hey,
I'm currently migrating from MUI 4 to version 5 and have encountered some weird behaviour.
I've got a two files tableFilterStyles.ts
and TableFilter.tsx
containing
tableFilterStyles.ts
import { Theme } from "@mui/material/styles";
export const tableFilterStyles = (theme: Theme) =>
({
root: {
position: "relative"
},
....
});
TableFilter.tsx
import { makeStyles } from 'tss-react/mui';
import { tableFilterStyles } from "../../styles";
const useStyles = makeStyles()(tableFilterStyles);
....
For some particular reason Typescript doesn't like the position.relative
object. "Type 'string' is not assignable to type '"relative"
Everything else works expect "position".
I guess I need to add a return type to the tableFilterStyles
function, maybe cssObjectByRuleNameOrGetCssObjectByRuleName
?
Do you have any idea?
If I pass the style directly it works
const useStyles = makeStyles()( (theme: Theme) => ({
root: {
position: "relative"
},
}));
Thanks!
The whole error message;
Argument of type '(theme: Theme) => { root: { position: string; }; checkboxes: { position: string; }; }' is not assignable to parameter of type 'Record<"root" | "checkboxes", CSSObject> | ((theme: Theme, params: void, classes: Record<never, string>) => Record<"root" | "checkboxes", CSSObject>)'. Type '(theme: Theme) => { root: { position: string; }; checkboxes: { position: string; }; }' is not assignable to type '(theme: Theme, params: void, classes: Record<never, string>) => Record<"root" | "checkboxes", CSSObject>'. Call signature return types '{ root: { position: string; }; checkboxes: { position: string; }; }' and 'Record<"root" | "checkboxes", CSSObject>' are incompatible. The types of 'root.position' are incompatible between these types. Type 'string' is not assignable to type '"relative" | "-moz-initial" | "inherit" | "initial" | "revert" | "unset" | "-webkit-sticky" | "absolute" | "fixed" | "static" | "sticky" | ("relative" | "-moz-initial" | "inherit" | ... 8 more ... | undefined)[] | Position[] | undefined'.
Context:
makeStyles
with tss-reactI'm used to passing classes to MUI components in a classes
object in order to overwrite styles.
However, the Material UI default classes always seems to be appended later, causing the custom styles to be ignored.
For example:
const useStyles = makeStyles()((theme: Theme) => ({
myButtonRoot: {
backgroundColor: "green",
},
}));
...
const { classes } = useStyles();
<Button color="primary" classes={{ root: classes.myButtonRoot }} />;
The resulting css will list that green background, but it will be overwritten by the own MUI Button styling, since that has a background of the primary theme color.
The custom makeStyles
classes should come after/have priority over the default ones of that component.
I know this is something i'm doing wrong, but I can't seem to figure it out. I just refactored everything to use the tss-api in mui 5. Pretty stumped
My structure is as follows
AppThemeProvider.tsx
import { CacheProvider } from '@emotion/react';
import { LocalizationProvider } from '@mui/lab';
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import { createTheme, ThemeProvider } from '@mui/material';
import * as React from 'react';
import createCache from 'tss-react/@emotion/cache';
import { defaultTheme, darkTheme } from '../../themes/default';
export const muiCache = createCache({
key: 'mui',
prepend: true
});
interface IAppThemeProviderProps {
themeType: 'dark' | 'light';
}
const AppThemeProvider: React.FC<IAppThemeProviderProps> = ({ children, themeType }) => (
<CacheProvider value={muiCache}>
<ThemeProvider theme={createTheme(themeType === 'light' ? defaultTheme : darkTheme)}>
{/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */}
<LocalizationProvider dateAdapter={AdapterDateFns}>
{children}
</LocalizationProvider>
</ThemeProvider>
</CacheProvider>
);
export default AppThemeProvider;
makeStyles.ts
import { createMakeStyles } from 'tss-react';
import { defaultTheme } from './themes/default';
import { ICustomTheme } from './themes/types';
function useTheme() {
// I need to cast this. Before createTheme turned my theme from ThemeOptions to type Theme
return defaultTheme as ICustomTheme;
}
export const { makeStyles } = createMakeStyles({ useTheme });
usage:
const useStyles = makeStyles()((theme: ICustomTheme) => ({
notificationSettingsContainer: {
margin: theme.spacing(1, 0, 2, 0),
padding: theme.spacing(2)
},
secondaryButton: {
borderRadius: theme.spacing(0.5),
margin: theme.spacing(1, 0, 0, 0),
textTransform: 'uppercase'
}
}));
I'm not sure what i'm missing here. Any help is greatly appreciated.
I am not sure if this is specific to tss-react or another emotion issue, but I am finding this happens with the typings
import makeStyles from '@ui/utils/makeStyles';
export default makeStyles()((theme, _params, css) => {
const s2 = {
position: 'absolute',
};
return {
s1: {
[`&:hover .${css(s2)}`]: {
color: 'red'
}
},
};
});
TS error:
No overload matches this call.
Overload 1 of 2, '(template: TemplateStringsArray, ...args: CSSInterpolation[]): string', gave the following error.
Argument of type '{ position: string; }' is not assignable to parameter of type 'TemplateStringsArray'.
Type '{ position: string; }' is missing the following properties from type 'TemplateStringsArray': raw, length, concat, join, and 17 more.
Overload 2 of 2, '(...args: CSSInterpolation[]): string', gave the following error.
Argument of type '{ position: string; }' is not assignable to parameter of type 'CSSInterpolation'.
Type '{ position: string; }' is not assignable to type 'CSSObject'.
Types of property 'position' are incompatible.
Type 'string' is not assignable to type 'Position | Position[]'.ts(2769)
I can work around by casting to CSSObject but its not great.
On material ui v4, there was a critical bug when used with nexjs and revalidated pages:
mui/material-ui#25307
It would lead to page layouts breaking in random ways due to orders of tags in head changing.
Fixing it was out of scope for MUI v4, and as @oliviertassinari pointed out there, the same issue was unlikely to appear in emotion since that uses prop cascading.
I'm wondering now if these issues are likely to come back when using tss-react:
What I was currently trying to solve with some of the the other issues I opened, is basically the makeStyles-defined classes having more priority than the MUI ones, although following the emotion cascading logic, it would be the opposite (Outside some component using the tss version of makeStyles first, then inside/after the MUI component defining its own styles)
So far, making the makeStyles-classes have more priority only worked by using two emotion caches, and essentialy relying on the order of elements in HEAD if I get things correctly
I will prepare a sandbox like the one in the issue above to see if I can observe the same behavior.
Hi,
I am trying to start a discussion to see if it is possible/feasible to create a codemod that would migrate from @material-ui/styles
makeStyles
and withStyles
to the tss-react
's equivalents.
The motivation is to help the developers to migrate from @materia-ui/core
(v4) to @mui/material
(v5) in a more automated way. From what I was seeing from time to time in the activity of the project, looks like most (all?) features that @material-ui/styles' API provided are already implemented. If this is the case, I would like to hear your opinion, on whether you think this could actually be automated, or there are things that are still depending on a person being involved when migrating.
Thanks!
Hey man :)
Now we are working on a project with micro-frontend architecture based on Webpack 5 Module Federation.
It's actually a single react project made up of 20 small projects.
Just a bit worried on the duplication on CSS classnames. Is it possible that there might be some duplicated classnames across these projects?
In material UI, there's a key attribute in emotion cache, as well as the prefix attribute in JSS which can avoid this problem.
export const muiCache = createCache({
key: "mui",
prepend: true
});
Please give us some advices, thanks mate :)
This issue overlaps a bit with what I described in #18, so I moved things here.
I was able to fix the makeStyles classes priority issue in some parts, by utilizing createCache
in there using the same key I used in _document and _app for it. (Actually using a shared helper createEmotionCache like done in the MUI repo example).
The custom makestyles function now looks like this:
const cache = createEmotionCache();
const { makeStyles } = createMakeStyles({
useTheme,
cache
});
While that works in dev, in a production build it actually breaks the server render (Fixed itself when the client side hydration kicks in). Removing that one line to pass a cache to createMakeStyles
makes SSR properly work again, however Im back to the issue of makeStyles classes having less priority than MUI ones.
Since Im defining my own cache with key, I wonder, if Looking at the code, this does work using some recently exposed context hook in emotion.cache
is not passed, would this lib actually pick up the correct cache from the provider, or create a new one under its key "tss-react"?
I also tried the opposite, not defining a cache and using getCache
everywhere + also removing my custom _document and using your utility for it instead, however with that I would have the same result, ssr works, but makeStyles classes have less priority.
...Which basically led me to two options:
A. The way getCache in tss works is wrong
B. Creating the cache in my makeStyles and passing it along createMakeStyles
is wrong, and my class order working now in dev is just a side effect.
Next I tried actually adding a second emotion cache, this time on purpose with a different cache key, then also wrapping _app in another, outer <CacheProvider
(the inner would be for mui) and in _document.js I also did the style extraction a second time for that second cache key (After the first one for the default styles, to gain priority )
However, the result is similar, passing a custom cache to createMakeStyles
just seems to break SSR.
I realise this issue has been mentioned previously but, I wanted to understand something.
I recently had an issue with the insertion order of styles, the base MUI styles were being inserted after the TSS styles and so, took precedence over the TSS styles. This caused issues where I was setting the display to be flex
but the MUI Container styles default to block
and so rendered in a top-down approach.
As you can see from the screenshot, the MUI Container styles are taking precedence. I tried one of the solutions recommended on another similar issue (to use &&
) and that worked. But I then realised that the insertion order was possibly off due to the TSS emotion server coming before the MUI emotion server in the emotionServers
array (from the docs):
const createMUICache = () =>
createCache({
key: "mui",
prepend: true,
});
....
const muiCache = createMUICache();
// sets TSS server first and then the MUI server after, meaning the MUI styles would take precedence?
const emotionServers = [getTssDefaultEmotionCache({ doReset: true }), muiCache].map(createEmotionServer);
.... // renders app
const css = emotionServers
.map(({ constructStyleTagsFromChunks, extractCriticalToChunks }) => {
const chunks = extractCriticalToChunks(html);
return constructStyleTagsFromChunks(chunks);
})
.join(""); // creates the styles but the TSS ones first and then the MUI ones
Noticing this, I tried it again by swapping around the two servers like below, rather than using the &&
approach:
const emotionServers = [muiCache, getTssDefaultEmotionCache({ doReset: true })].map(createEmotionServer);
and that then fixes the insertion order (see the image below).
So, my question is just, is there a particular reason as to why the MUI emotion server should come after the TSS one, or is it fine to have the TSS one last? If so, should the docs be updated for this?
Let me know if you would like any more information, this is the only issue I have come across with TSS and I think it is an awesome package and nice to see there is still support out there for JSS approaches with MUI! :)
Any way we can reuse styles that rely on props/state?
const { useClassNames } = createUseClassNames()((props) => ({
link: {
...
'&:hover': {
extend: 'activeLink', // JSS solution
},
},
activeLink: {
// some CSS that relies on props
},
}));
In this line, getDependencyArrayRef(params)
should be replaced with just params
.
First reason:
getDependencyArrayRef
uses json serialization which can VERY easily throw exceptions if it is not a plain object or contains circular references
Second reason:
getDependencyArrayRef
uses json serialization which has a huge negative performance impact again which counteracts the optimization
Third reason:
It's not even required. If the object is the same reference, we should also expect that it's values are the same (same as with React's own hooks like useEffect
or useMemo
).
This would of course break if someone actually does something crazy like this:
const styleParams = React.useRef({ color: 'red' });
const { classes } = useStyles(styleParams);
// ... somewhere else
styleParams.color = 'blue'
However, as mentioned before, this is the very same for React's own hooks as well so it does not make sense to try optimizing for this.
Fourth reason:
If a user does want to use params but also benefit from performance improvements, there is a simpler and way more performant way to do so:
const { classes } = useStyles(
useMemo(() => ({
color: props.color,
}), [props.color])
);
To make class names more readable for development like they are in material-ui, I would like to propose the following adjustment to make styles, to make it more compatible with old material-ui v4 makeStyles:
const useStyles = makeStyles({ name: 'MyComponent' })(theme => ({
root: {
color: 'red',
},
child: {
color: 'blue',
},
}));
which would internally expand the styles object to the equivalent to
const useStyles = makeStyles()(theme => ({
root: {
label: 'MyComponent-root',
color: 'red',
},
child: {
label: 'MyComponent-child',
color: 'blue',
},
}));
which would result in the following class names:
.tss-react-s0z026-MyComponent-root { color:red; }
.tss-react-pc49kh-MyComponent-child { color:blue; }
The following adjusted version of makeStyles would be able to perform this logic (which is what I am currently using and everyone else can easily patch into their own app as well:
const { useStyles } = createMakeStyles({ useTheme });
export default function makeStyles<Params = void>(opt?: { name?: string; }) {
const labelPrefix = opt?.name ? `${opt.name}-` : '';
return function <RuleName extends string>(
cssObjectByRuleNameOrGetCssObjectByRuleName: Record<RuleName, CSSObject> | ((theme: Theme, params: Params, createRef: () => string) => Record<RuleName, CSSObject>)
) {
const getCssObjectByRuleName = typeof cssObjectByRuleNameOrGetCssObjectByRuleName === 'function' ?
cssObjectByRuleNameOrGetCssObjectByRuleName :
() => cssObjectByRuleNameOrGetCssObjectByRuleName;
function useStylesHook(params: Params) {
const { cx, css, theme } = useStyles();
let count = 0;
function createRef() {
return `tss-react-ref_${count++}`;
}
const cssObjectByRuleName = getCssObjectByRuleName(theme, params, createRef);
const classes = Object.fromEntries(Object.keys(cssObjectByRuleName).map(ruleName => {
const cssObject: CSSObject = cssObjectByRuleName[ruleName as RuleName];
if (!cssObject.label) {
// Assign label to class name if none is provided
cssObject.label = labelPrefix + ruleName;
}
const className = css(cssObject);
return [ruleName, className];
})) as Record<RuleName, string>;
return { classes, theme, css, cx, };
}
return useStylesHook;
};
}
I think this would be a great addition to this library to make it easier to work with 👍
Hi, thanks for all the hard work first of all. But would it be much to ask for some more extensive examples as to what is expected for this to work? I expect this lib has to order every bit off css to guarantee specificity, but following the docs it is not made clear what the minimal requirements are to do so.
I'm using the material ui createStyles and try to inject the complete styles of body1.
const useStyles = makeStyles()((theme) => ({
root: {
...theme.typography.body1,
},
}))
This will however give an error:
Argument of type '(theme: Theme) => { root: { backgroundColor: string; '@font-face'?: Fontface | Fontface[] | undefined; accentColor?: AccentColor | undefined; ... 783 more ...; vectorEffect?: VectorEffect | undefined; }; }' is not assignable to parameter of type 'Record<"root", CSSObject> | ((theme: Theme, params: void, classes: Record<never, string>) => Record<"root", CSSObject>)'.
Type '(theme: Theme) => { root: { backgroundColor: string; '@font-face'?: Fontface | Fontface[] | undefined; accentColor?: AccentColor | undefined; ... 783 more ...; vectorEffect?: VectorEffect | undefined; }; }' is not assignable to type '(theme: Theme, params: void, classes: Record<never, string>) => Record<"root", CSSObject>'.
Call signature return types '{ root: { backgroundColor: string; '@font-face'?: Fontface | Fontface[] | undefined; accentColor?: AccentColor | undefined; alignContent?: AlignContent | undefined; ... 782 more ...; vectorEffect?: VectorEffect | undefined; }; }' and 'Record<"root", CSSObject>' are incompatible.
The types of 'root' are incompatible between these types.
Type '{ backgroundColor: string; '@font-face'?: Fontface | Fontface[] | undefined; accentColor?: Property.AccentColor | undefined; alignContent?: Property.AlignContent | undefined; ... 782 more ...; vectorEffect?: Property.VectorEffect | undefined; }' is not assignable to type 'CSSObject'.
Property ''@font-face'' is incompatible with index signature.
Type 'Fontface | Fontface[]' is not assignable to type 'CSSInterpolation'.
Type 'Fontface' is not assignable to type 'CSSInterpolation'.
Type 'Fontface' is not assignable to type 'CSSObject'.
Index signature for type 'string' is missing in type 'FontFace<0 | (string & {}), string & {}> & { fallbacks?: FontFace<0 | (string & {}), string & {}>[] | undefined; }'.ts(2345)
I've ran into this issue in another situation in the past and in that case it was a version mismatch with csstype
, but I've tripple checked but this doesn't seem to be the case.
It is because of this line in Material UI, if I temporarily comment this out, it wont throw the error: https://github.com/mui-org/material-ui/blob/master/packages/mui-material/src/styles/createTypography.d.ts#L38
We should be able to do:
const { classNames } = useClassNames();
instead of
const { classNames } = useClassNames({});
When we have no state argument, e.g:
const { useClassNames } = createUseClassNames()(
theme=>({
//...
})
);
Good day, I'm trying to test a component that makes use of tss (I'm migrating from the materialUI v4 makeStyles
), while this is working fine during development, I'm encountering an issue when trying to test a component that makes use of tss
The error is the following:
TypeError: (0 , cache_1.default) is not a function
12 |
13 | export const DummyElement = () => {
> 14 | const { classes, cx } = useStyles();
| ^
15 | return <div className={cx(classes.root)}>DUMMY</div>;
16 | };
17 |
This is with material UI v5, I followed all the steps in the readme, I created a minimal reproducible example in this repository: https://github.com/lucacampana-whs/tss-react-issue-jest-cache
Hi
Thanks a lot for this tool and your examples and docs.
I am trying to use tss-react with next
I read your docs here https://docs.tss-react.dev/ssr/next.js and checked-out the example here https://github.com/garronej/tss-react/tree/main/src/test/apps/ssr
When I move the cache provider to _app.tsx
the styles no longer work when server side rendering (I get an initial un-styled page from the server which is then styled on the client)
Here is a my code.
https://github.com/ziggy6792/tss-react/tree/provider-in-app
Thanks a lot
We are upgrading the company's UI library, which is a wrapper of MUI components, and tss-react is used to customize the styles with dynamic themes. Also we use makeStyles, useStyles etc. within the UI library repository to customize the MUI styles:
export const { makeStyles, useStyles, withStyles } = createMakeAndWithStyles({ useTheme });
The issue is, when consuming the customized components from the UI library from our main project, and we got this error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
I guess the reason is because the "useStyles" within the UI library repo does not work because it is actually a wrapper of the "useTheme" hooks, and it should be created in the main repo. However we need to use it in the UI library which is causing issue.
Any solutions can help?
Are there going to be any breaking changes after updating React to v18 in peer dependencies?
Currently, I'm unable to install tss-react
in my React v18 project:
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0 || ^17.0.2" from [email protected]
npm ERR! node_modules/tss-react
npm ERR! tss-react@"^3.6.0" from the root project
There is a package to detect unused styles with @mui/styles: https://github.com/jens-ox/eslint-plugin-material-ui-unused-classes. Can we have one adjusted to tss-react?
Also, unrelated question: does GlobalStyles in this package differ from one exported from @mui/material?
we are using tss-react to work with storybook,
this is the preview.js file
import React from "react";
import { ThemeProvider } from "@mui/material/styles";
import { ThemeProvider as Emotion10ThemeProvider } from "emotion-theming";
import { Themes } from "../src/theme";
import { CssBaseline } from "@mui/material";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i
}
},
docs: {
source: { type: "dynamic", excludeDecorators: true },
inlineStories: false,
iframeHeight: "700px"
}
};
const withThemeProvider = (Story, context) => {
return (
<Emotion10ThemeProvider theme={Themes.Default}>
<ThemeProvider theme={Themes.Default}>
<CssBaseline />
<Story {...context} />
</ThemeProvider>
</Emotion10ThemeProvider>
);
};
export const decorators = [withThemeProvider];
in one of the component I override the chip style like this
const useStyles = makeStyles()(
({ palette }) => ({
root: {
border: "0.1rem solid",
textAlign: "center",
padding: "0 0.5rem",
borderRadius: "0.2rem",
whiteSpace: "nowrap",
borderColor: "#ff0000",
backgroundColor: "#ffffff",
color: "#ff0000"
}
})
);
const CustomChip = ({
text,
}: {
text: string;
}) => {
const { classes, cx } = useStyles();
return (
<Typography variant="body2" className={classes.root}>
{text}
</Typography>
);
};
when it builds I got two css classes for that element:
.css-1vjo9lu {
margin: 0px;
font-size: 1.4rem;
line-height: 1.71;
color: rgb(38, 50, 56);
letter-spacing: normal;
font-weight: 400;
font-family: "Cera Pro", sans-serif;
}
.tss-1ev1aua {
border: 0.1rem
solid rgb(0, 104, 200);
text-align: center;
padding: 0px 0.5rem;
border-radius: 0.2rem;
white-space: nowrap;
background-color: rgb(255, 255, 255);
color: rgb(0, 104, 200);
}
The first css class was the mui typography override and the second one was the makestyle override in the component.
The issue is the color should be the one in makestyle but it takes the typography color.
Please let me know where we did it wrong or this is a bug in storybook or tss-react.
Many thanks.
Our code heavily depends on custom cache, and we have multiple emotion caches in the whole application. It makes it impossible to provide our custom cache when calling createMakeStyles
First of all, thanks for creating a great library!
I'm migrating a project from using jss towards tss-react but I'm struggling with the different style order when using cx
.
Codesandbox is here.
I have two components (Parent and Child), and I want to override the objectFit
property of img
tag from Parent.
const useStyles = makeStyles()(() => ({
logo: {
width: "100%",
height: "53px",
objectFit: "contain"
}
}));
export const Parent = () => {
const { classes } = useStyles();
const src =
"https://ichef.bbci.co.uk/news/640/cpsprodpb/17A21/production/_85310869_85310700.jpg";
return (
<>
<Box width="200px" height="106px">
<Child src={src} className={classes.logo} />
</Box>
</>
);
};
import { makeStyles } from "tss-react/mui";
const useStyles = makeStyles()(() => ({
img: {
objectFit: "cover"
}
}));
type Props = {
className?: string;
src: string;
};
export const Child = ({ className, src }: Props) => {
const { classes, cx } = useStyles();
return <img src={src} className={cx(className, classes.img)} />;
};
The style generated by cx is as follows.
.tss-5qt1p9-logo-img {
width: 100%;
height: 53px;
object-fit: contain;
object-fit: cover; // The style declared in the child component wins.
}
// Expected result
.tss-5qt1p9-logo-img {
width: 100%;
height: 53px;
object-fit: cover;
object-fit: contain;
}
Using styled
instead of makeStyles
get the expected result.
Hi @garronej,
First of all I'd like to thank you for all the work you've put into this. I've been using this library from the start and it's pretty much the thing that made migration to MUI v5 possible for me. The amount of work and love you've put into this is inspiring, so thank you very much!
I had previously opened an issue here with regards to an error I was having with the fromEntries polyfill included in this lib (specifically on ie11). Long story short the polyfill included here requires Symbol.Iterator
, which does come with recommended ie11 polyfills for react apps. Our project doesn't use those, we instead use the polyfill.io service which allows us to reduce bundle size and also move polyfill dependencies elsewhere. For reference, we ended up including the Object.fromEntries polyfill from there which doesn't have other polyfill dependencies (https://github.com/Financial-Times/polyfill-library/blob/master/polyfills/Object/fromEntries/polyfill.js).
Recently with further upgrades we've also had to include Array.find and Proxy, the latter one being slightly harder as it's not really included in any of the major polyfill providers, so it requires that specific package which you linked. Proxy, specifically, is a problem not just in ie11, but in several older browsers, which we started to see errors for in our logs. I know it's all in your README and adding the required polyfills is not particularly hard, but specifically in our case since we run automatic dependency upgrading in our pipelines we tend to see these issues come up in production, even if it only impacts a small percentage of users.
So my suggestion would be if there is a possibility of having a version that doesn't require any of these polyfills. I haven't looked into the code to see what Proxy or Array.find is doing, and if there are other ways to do the same thing, so please excuse my request if it doesn't make any sense. I just thought I'd put the suggestion out there since, for example, even though MUI v5 itself doesn't support ie11 and older browsers, it does actually work on them bar some stying issues or other very specific problems.
Again, please ignore if this doesn't make any sense, and thank you for the great work.
Fernando
Hey 😄 Sorry to bother, but I'm quite confused.
I've been trying to use this library with Gatsby JS (a popular static site generation framework, similar to Next JS). I've messed around with gatsby-plugin-emotion
and gatsby-plugin-material-ui
, but my styles never seem to be server-side rendered in production mode — when I run gatsby serve
and then open up the page, there is always a "flash of unstyled content".
I'm guessing the problem is that the SSG side doesn't have access to the emotion cache from tss-react? The Gatsby documentation has a page for emotion but they don't mention anything about a custom cache, so I'm a bit stuck unfortunately 😅
Would it perhaps be possible to have an example of a tss-react setup with Gatbsy? I'm sure it would be helpful to lots of people 😄
Best,
I would like to see an example how to integrate tss-react based on this: https://github.com/mui-org/material-ui/tree/next/examples/nextjs-with-typescript
The current way things work is a bit confusing to me:
The example in the MUI docs has a shared util createEmotionCache
which when called, always returns the result of emotions createCache
function.
Now both _app and _document call that, _document uses it once in initial props, _app stores it global to the file.
The example here acts in a different way: The createDocument
function returns getInitialProps logic, which internally uses getCache
.
There is no _app, but I assume looking at what index.tsx does is the same what _app would do per page. index.tsx also uses the same getCache
function.
What getCache
basically does, is it stores a cache
globally, and returns in or if it doesn't exist yet, create it using createCache
from emotion.
That seems to be different from what the mui example does, since _document and _app receieve their own instance here.
Some more background to why this example would help understanding the cache utilization is in #19.
Having an example using the same structure as the MUI one would help a lot to understand the differences there.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.