Material-UI injectFirst doesn't work with storybook - reactjs

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

Related

Convert Style ts file from Material V4 to V5

I am trying to migrate from material v4 to v5, the issue i am facing is previously I had a styles.ts file that I would import into my component and the start of the class looked like this:
import {
defaultFont,
primaryColor,
infoColor,
successColor,
warningColor,
dangerColor,
grayColor,
} from "../material-kit-pro-react";
import { makeStyles, Theme, createStyles } from "#material-ui/core";
export const typographyStyle = makeStyles((theme: Theme) => createStyles({
defaultFontStyle: {
I then updated the imports to the following:
import {
defaultFont,
primaryColor,
infoColor,
successColor,
warningColor,
dangerColor,
grayColor,
} from "../material-kit-pro-react";
import { makeStyles, Theme } from "#mui/material/styles";
import { createStyles } from '#mui/styles';
export const typographyStyle = makeStyles((theme: Theme) => createStyles({
This uses the #mui imports, but in my actual class I get the following error:
const typographyStyle: never
import typographyStyle
This expression is not callable.
Type 'never' has no call signatures.
My component is pretty basic and it looks like this:
import React from "react";
import { typographyStyle } from "assets/jss/material/typography/typographyStyle";
export interface TypographyProps {
children?: React.ReactNode;
}
export function Danger(props: TypographyProps) {
const classes = typographyStyle();
const { children } = props;
return (
<div className={classes.defaultFontStyle + " " + classes.dangerText}>
{children}
</div>
);
}
The error comes from const classes = typographyStyle();
That´s occuring because makeStyles import has been moved to #mui/styles, so your import must be:
import { Theme } from "#mui/material/styles";
import { createStyles, makeStyles } from "#mui/styles";
Also, according to MUI documentation:
#mui/styles is the legacy styling solution for MUI. It depends on JSS as a styling solution, which is not used in the #mui/material anymore, deprecated in v5. If you don't want to have both emotion & JSS in your bundle, please refer to the #mui/system documentation which is the recommended alternative.
And
We have replaced JSS with emotion as a default styling solution while adding support for styled-components at the same time. We recommend migration your customization from JSS/makeStyles/withStyles to the new APIs: styled and the sx prop
So, with MUI v5 you should probably go with sx or styled instead makestyles. You can take a better look here and here.

How can I use useTheme in Material UI 5?

I just started using Material UI 5.0.4 (with styled-components), and I wanted to access the theme in a component. I looked online and saw useTheme, so I checked the docs and found it - #mui/styles/useTheme. However, it was the legacy documentation, and #mui/styles does not exist in MUI 5. So, I looked at #mui/system instead, and found the section "Accessing the theme in a component". However, this just points back to the legacy documentation!
After the docs didn't seem to help me, I decided to use Visual Studio Code's "Quick Fix" feature, where if you hover over the function, VSCode will give you a list of options to import. Here is the list of options I tried, and why they didn't work:
#mui/material/styles/useTheme - Returns the default theme object, no matter what. Looking into the source code, this is literally what it does - it switches to the default theme, and then returns the theme.
#mui/material/private-theming/useTheme - This just returns null. I feel like I shouldn't be accessing this anyway (it says private-), but I tried it anyway.
#mui/system/useTheme - This is what I was hoping would work. However, this is also probably the weirdest one. It gives me the default theme, but it excludes many properties. For example, it only provided palette.mode, and there are no other keys under palette than that. (You can see the whole thing below)
{
"breakpoints": {
"keys": ["xs", "sm", "md", "lg", "xl"],
"values": { "xs": 0, "sm": 600, "md": 900, "lg": 1200, "xl": 1536 },
"unit": "px"
},
"direction": "ltr",
"components": {},
"palette": { "mode": "light" },
"shape": { "borderRadius": 4 }
}
styled-components/useTheme - Returns undefined.
#mui/styled-engine-sc/useTheme - Returns undefined. (I have a feeling this is the same thing as styled-components/useTheme.)
Those were all the suggestions that VSCode could give me, apart from things like #mui/system/useTheme vs #mui/system/useTheme/useTheme (which is the same thing). I also tried googling stuff but it would always be really old, like:
Issue #8958 on GitHub for MUI which references #material-ui/core/styles which is v4 and not in v5
SO question labelled "access the theme from outside material-ui component" which references the legacy docs (#material-ui/styles does not exist anymore, and #mui/material/styles/useTheme does not work as explained above)
Please, if someone knows, how do you get the theme in a component in MUI 5?
It turns out that the correct useTheme is #mui/material/styles/useTheme, and you cannot use useTheme in the same component that you do the ThemeProvider in. For example, this:
const App = () => {
const theme = useTheme();
return (
<ThemeProvider theme={myTheme}>
<Box bgcolor={theme.palette.background.default} width={100} height={100} />
</ThemeProvider>
);
};
Will not work properly. However, this:
const MyComponent = () => {
const theme = useTheme();
return <Box bgcolor={theme.palette.background.default} width={100} height={100} />;
};
const App = () => (
<ThemeProvider theme={myTheme}>
<MyComponent />
</ThemeProvider>
)
Will work properly, as useTheme is used in a separate component.
Just in case anyone wonder why you can't use useTheme in the same component as
ThemeProvider, it is because useTheme has to be in a component wrapped by ThemeProvider. The context isn't available to components outside of that component tree.
For anybody still struggling with this, I got it working by importing createTheme, ThemeProvider and useTheme all directly from #mui/material...
theme.js:
import { createTheme } from '#mui/material';
export const theme = createTheme({
...
});
_app.tsx (I'm using next.js)
import { CssBaseline, ThemeProvider } from '#mui/material';
import type { AppProps } from 'next/app';
import React from 'react';
import { theme } from '../theme';
function MyApp({ Component, pageProps }: AppProps) {
return (
<React.StrictMode>
<CssBaseline />
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</React.StrictMode>
);
}
export default MyApp;
navigation.tsx (my component with useTheme)
import { Drawer, useTheme } from '#mui/material';
import React from 'react';
const Navigation = (): JSX.Element => {
const theme = useTheme();
const drawerSx = {
'& .MuiDrawer-paper': {
background: `linear-gradient(to bottom right, ${theme.palette.primary.main}, ${theme.palette.primary.dark})`,
},
};
return (
<Drawer sx={drawerSx} variant="permanent">
...
</Drawer>
);
};
export default Navigation;
I was struggling before I did this, it was only applying the default theme, not the custom one.
If you are using the useTheme from #mui/material/styles and is still not working check if you un your AppTheme.jsx (or where you are using the ThemeProvider) check that you are using the ThemeProvider from #mui/material/styles and not the ThemeProvider of #emotion/react. That why in my case (using MUI v5) useTheme wasn't working with my own theme (in particular with my custom breakpoints).
As already answered, your first usage of useTheme must be wrapped inside ThemeProvider in a parent component.
(useTheme needs a theme provider to draw from.)
Technically, you can useTheme in the same component as ThemeProvider, as long as the component is used in a parent component that wraps it with ThemeProvider
Example:
const InnerComponent = () => {
// Use the theme from `App` below
const appTheme = useTheme();
// Local Theme. (May want to wrap with `useMemo` too.)
const innerTheme = createThemeV5(appTheme, {
// Local theme overrides
});
return (
<ThemeProvider theme={innerTheme}>
<Box />
</ThemeProvider>
);
}
const App = () => {
return (
<ThemeProvider theme={myTheme}>
<InnerComponent />
</ThemeProvider>
);
};

MUI v5 - Styled() and typescript

I'm using MUI v5 and decided to migrate from makeStyles to styled() utility function. I have this styled div:
import { styled } from "#mui/system";
export const WelcomeDiv = styled("div")({
background: "blue",
});
I try to import it use it this way:
return (
<WelcomeDiv>
</WelcomeDiv>
);
Thing is, I've just started using typescript and not sure how to implement it.

material-ui styles with next.js

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

How do I pass my components theme through storybook?

I need to pass a theme that is used in components but I get a syntax error.
My .storybook/config.js:
import { configure, addDecorator } from '#storybook/react'
import React from 'react'
import theme from '../src/theme'
import { ThemeProvider } from 'styled-components'
import '../src/styles/index.css'
addDecorator(story => <ThemeProvider theme={theme}>{story()}</ThemeProvider>)
function loadStories() {
const req = require.context('../src', true, /\.stories.js$/)
req.keys().forEach(filename => req(filename))
}
configure(loadStories, module)
Here's the full error:
Have you tried making the theme provider as a separate file without using the decorators? The below is a Styled-components and typescript implementation.
import React from 'react';
export const Container = ({ title, children }) => {
return (
<StoryWrapper>
<GlobalStyle theme={themes.default} />
<ThemeProvider theme={themes.default}>
<MainWrapper>
<Header>{title}</Header>
{children}
</MainWrapper>
</ThemeProvider>
</StoryWrapper>
);
};
I never used the add decorator feature and this was the config implementation I used, it is set up for tsx though.
import { addParameters, configure, addDecorator } from '#storybook/react';
import { withKnobs } from '#storybook/addon-knobs';
const req = require.context('../src', true, /.stories.tsx$/);
function loadStories(){
req.keys().forEach(req);
}
addDecorator(withKnobs);
configure(loadStories, module);
Seems like Babel is confused that you're using JSX in a JS file.
Try renaming config.js to config.jsx. The file extension should instruct babel to treat it as a JSX file.

Resources