material-ui styles with next.js - reactjs

I'm having trouble getting material-ui styles to work correctly with next.js. When I change a style and then save while running the dev server the styles get applied, but if I reload the page, save any other change, or restart the server the default material-ui styles overwrite my custom styles. Also, whenever I use the Box material-ui component I see this error in the console:
react-dom.development.js:67 Warning: Prop `className` did not match. Server: "MuiBox-root MuiBox-root-3 makeStyles-boxStyles-1" Client: "MuiBox-root MuiBox-root-4 makeStyles-boxStyles-1"
Here is my _document.tsx: https://pastebin.com/wJD9jyZQ
Here is my _app.tsx: https://pastebin.com/s8Ys01kb
Here is my index.tsx: https://pastebin.com/t5Z9QGpP
Here is index.styles.ts (where my custom styles are): https://pastebin.com/qe7M5ysq

In the _document.js file you need to enhance the App with the ServerStyleSheets object.
For Material UI v4 you need to import:
import { ServerStyleSheets } from '#material-ui/core/styles';
For Material UI v5 you need to import:
import { ServerStyleSheets } from '#material-ui/styles';
Then later down in the file:
const sheets = new ServerStyleSheets();
...
enhanceApp: (App: any) => (props) => sheets.collect(<App ... />),
See this v4 example
The above worked for me with v5 even though the v5 example did not include ServerStyleSheets

Related

MUI v5 + styled() + ListItemButton: property 'to'/'component' does not exist

In the migration from MUI v4 to v5 I'm hitting this road block: in v4 I've used makeStyles(), but now want to fully migrate to styled(): I can't get Typescript to accept a styled(ListItemButton)(...) with to= and component= properties.
I've seen and read MUI's guide on Wrapping components which actually made things even less clear to me; admittedly, I'm neither a Typescript nor MUI wizard. My confusion is fueled by the guide's examples being incomplete, such as obviously lacking some non-obviously imports which seem to need imported symbol renaming, so automatic completion won't work or turn up multiple candidates.
This is a minimized example triggering the Typescript error, see below for codesandbox link.
import React from "react"
import {
Avatar,
Link,
ListItemAvatar,
ListItemButton,
styled
} from "#mui/material"
const ListItemButtonLink = styled(ListItemButton)(({ theme }) => ({
// ...here be styling
}))
interface LinkedListItemProps {
path: string
}
export const LinkedListItem = ({ path }: LinkedListItemProps) => {
return (
<ListItemButtonLink key={42} dense to={path} component={Link}>
<ListItemAvatar>
<Avatar>A</Avatar>
</ListItemAvatar>
Here Be Item
</ListItemButtonLink>
)
}
I'm totally out in the dark in how to get this working, as the Guide example doesn't pass Typescript checking either.
Looking through the MUI issues I found an issue that tackled the Guide issue, but doesn't really seem to fix it in a way that I could use.
I've also seen and read MUI button with styled-components including react router Link, but the solution is basically the non-Typescript guide version of a kind.
(updated)
You're using Link from the MUI package, this Link is just an anchor element but with better style integration with MUI theme, The Link you might want to use is from react-router which has a to prop, so change your code to:
import { Link } from "react-router-dom";
If you use styled and want the resulted component to have the prop to from react-router, you can add the generic type parameter to the HOC:
import { Link, LinkProps } from "react-router-dom";
const ListItemButtonLink = styled(ListItemButton)<LinkProps>(({ theme }) => ({
// ...here be styling
}));

After upgrading from MaterialUI v4 to MaterialUI v5 - lack of default theme causing mount tests to fail. How to reinstate a default theme?

In in my mocha test code I have have lots of tests that use mount.
enzyme.mount(<SomeComponentThatUsesMUI />);
This worked fine in material-ui v4. However in v5 due to the lack of a default theme, any code in SomeComponentThatUsesMui that attempts to access the theme
Now I know could wrap the component with a ThemeProvider :
enzyme.mount(<ThemeProvider theme={} ><<SomeComponentThatUsesMUI /><ThemeProvider />);
However this means that subsequent test code will need adjusting, and this is 100+ tests. (5% of my tests).
Is there a way to reinstate, for the unittests, a default theme?
Warning: This answer modifies two internal fields (as they are in React 17.0.2).
This will break if the React Context object changes internally
The cause of the null default theme is from:
#mui/private-theming/node/useTheme/ThemeContext.js
const ThemeContext = /*#__PURE__*/React.createContext(null);
When the createContext is called for ThemeContext, no default theme is set.
So one can add in unittest setup code (before your unittests run):
import { createTheme } from '#mui/material/styles';
import('#mui/private-theming/node/useTheme/ThemeContext.js').then(ThemeContext =>
{
let defaultTheme = React.createContext(createTheme({}));
if (ThemeContext.default)
{
ThemeContext.default._currentValue = defaultTheme._currentValue;
ThemeContext.default._currentValue2 = defaultTheme._currentValue2;
}
});
This modifies two internal fields, _currentValue, and _currentValue2 to adjust the default ThemeContext to be equivalent to
const ThemeContext = /*#__PURE__*/React.createContext(createTheme({}));
Although this answer requires modifying each mount statement,it doesn't require any subsequent changes to test code.
Provide wrappingComponent and wrappingComponentProps args to mount:
let wrapper = enzyme.mount(<SomeComponentThatUsesMUI />,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: createTheme({}) }
});
SomeComponentThatUsesMUI now has a theme, but wrapper still refers to SomeComponentThatUsesMUI and not ThemeProvider.

Material-UI styles overriding my styles cause of ordering

Hello there fellow programmers.
I am currently experiencing an issue with my own styles created using makeStyles(...). What's happening is; when I import my styles constant which is exported from another module, the style block is placed before the other MUI styles which causes mine to be overriden. I have not yet been able to find any solution to this what so ever and that is why I am here today, trying to figure out how I could possibly go my ways to fix this. Here's an image that contains the order of the style blocks that is playing with my mind currently.
PLEASE NOTE: The style block that's mine is the one with the data meta of makeStyles. Another thing is, I've attempted to use StylesProvider.
IMPORTANT: This only happens when uploaded. It works just fine on localhost, but this is the outcome of being uploaded to vercel.
Here's two examples of usages:
import { Theme } from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles';
const DarkTheme = makeStyles((theme: Theme) => ({
mdButton: {
backgroundColor: "#404040"
},
}));
export default DarkTheme;
import {Button} from '#material-ui/core';
import React from "react";
import DarkTheme from 'DarkTheme';
export default function Example() {
const styles = DarkTheme();
return (
<Button className={styles.mdButton}>
Example
</Button>
)
}
Any help is appreciated, thank you in advance.
As you may know, f you have two CSS classes applied to the same element with the same degree of specificity, then the winner will be the CSS class that is defined last within the document (based on the order of the elements in the , NOT the order of the class name strings in the class attribute of the element being styled). As: Material UI v4 makeStyles exported from a single file doesn't retain the styles on refresh
So, you can change order of imports. Import Button before import style.
Let try with what is different from:
import React from "react";
import {Button} from '#material-ui/core';
import DarkTheme from 'DarkTheme';
and
import React from "react";
import DarkTheme from 'DarkTheme';
import {Button} from '#material-ui/core';
makeStyles returns a function which must be called as a React hook. So please name it in consequence.
const useDarkTheme = makeStyles((theme: Theme) => ({
mdButton: {
backgroundColor: "#404040"
},
}));
export default useDarkTheme;
https://reactjs.org/docs/hooks-intro.html
If you don't want to use hooks you can check withStyles alternative: https://material-ui.com/styles/basics/#higher-order-component-api
You can also check how CSS injection works in material-ui https://material-ui.com/styles/advanced/#css-injection-order (tl;dr: order of makeStyles calls matter).

React-JSS with material-ui and server side rendering

I have a react application and a custom component library built using material-ui. Components within the react application itself uses react-jss for styling and I'd like to avoid too many references to material-ui in the app itself just in case I wish to replace material-ui in the future.
From what I've read it seems that Material-UI uses a wrapped version of JSS so they should be compatible, but I'm unable to extract both my own styles as well as those from material-ui. I seem to be able to extract one or the other though...
I've followed the React-JSS Server Side Rendering guide and that works fine, but I'm wondering how I can most easily combine styles from both without having to resort to using #material-ui/core/styles everywhere.
Here is my bootstrap component that registers the JssProvider in my app:
export const Bootstrap: React.FC<IBootstrapProps> = ({
sheetsRegistry,
generateId
}) => {
return (
<JssProvider registry={sheetsRegistry} generateId={generateId}>
<App />
</JssProvider>
)
}
export default Bootstrap
The sheetsRegistry and generateId props comes from a render function called directly from an express handler:
const render = (props: ISSRRenderProps) => {
const sheetsRegistry = new SheetsRegistry()
const generateId = createGenerateId()
const appContent = renderToString(
<Bootstrap
sheetsRegistry={sheetsRegistry}
generateId={generateId}
/>
)
return {
appContent,
styles: sheetsRegistry.toString()
}
}
This gives me all my custom styles in the returned styles property which I in turn dump to the page without issues. The problem comes when I try to combine this with #material-ui. Simply including a component from the library does not give me it's styles server side though they do render on the client after a FOUC.
Using the server rendering guide from material-ui and modifying my Bootstrap component does give my the material-ui styles, but now my own styles are not collected. This also adds a single reference to material-ui in my react app, but if that is what it takes I can live with it as long as sub-components don't have to use material-uis style tools.
import { ServerStyleSheets } from "#material-ui/core/styles"
export const Bootstrap: React.FC<IBootstrapProps> = ({
sheetsRegistry,
generateId,
...rest
}) => {
const sheets = new ServerStyleSheets()
sheetsRegistry?.add(sheets as any)
return sheets.collect(
<JssProvider registry={sheetsRegistry} generateId={generateId}>
<App {...rest} />
</JssProvider>
)
}
Is there a way to get the best of both worlds? I'd love to use Material-UI components in my UI library and extract all styles globally for server side rendering.
I've been able to come up with a "workaround" by modifying my render function to collect material-ui styles separately and just concatenating them:
import { ServerStyleSheets } from "#material-ui/core/styles"
const render = (props: ISSRRenderProps) => {
const sheetsRegistry = new SheetsRegistry()
const generateId = createGenerateId()
const muiStyles = new ServerStyleSheets()
const appContent = renderToString(
muiStyles.collect(
<Bootstrap
sheetsRegistry={sheetsRegistry}
generateId={generateId}
/>
)
)
return {
appContent,
styles: muiStyles.toString() + sheetsRegistry.toString()
}
}
The reason believe this is a workaround and may not be the best solution is that I still have to separately handle my own styles vs material-ui styles. Seeing as they both use JSS behind the scenes I still think it should be possible to seamlessly collect both in the same sheetsRegistry, but this works for now.

Material-UI injectFirst doesn't work with storybook

I'm using material-UI with styled-components and according to documentation, in order to override material styles it's required to add this injectFirst attribute:
however when trying to use this approach inside storybook environment it does not work as expected and the JSS styles are still injected after styled-components.
.storybook/config.js:
import React from 'react'
import {configure, addDecorator} from '#storybook/react'
import { StylesProvider } from '#material-ui/styles'
addDecorator(storyFn => (
<StylesProvider injectFirst>
{ storyFn() }
</StylesProvider>
));
const req = require.context('../packages', true, /.story.js$/);
function loadStories() {
req.keys().forEach((filename) => req(filename));
}
configure(loadStories, module);
DOM:
styled-components style attribute is still before JSS
I was not using styled-components, but for MUI + CSS Modules with Storybook v6 the following configuration made it so MUI styles were not overriding our own styles.
.storybook/preview.js:
import { StylesProvider } from '#material-ui/styles'
export const decorators = [
(Story) => (
<StylesProvider injectFirst>
{Story()}
</StylesProvider>
),
]
We had exiting config for parameters in that file from another add-on and I just put the above code in there with it no problem. You may also need to import React in there too depending on how you have things setup (we did not).

Resources