How do I pass my components theme through storybook? - reactjs

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.

Related

Uncaught TypeError: can't access property "button", theme.typography is undefined

I'm setting up a react application using MUI, and since we will be using styled components for our own custom styles & containers, I figure I may as well configure MUI with styled-components as per the docs. We will also be using yarn.
The package.json is defined as such:
{
"name": "app",
"version": "0.1.0",
"private": true,
"dependencies": {
"#mui/material": "^5.4.2",
"#mui/styled-engine": "npm:#mui/styled-engine-sc#^5.4.2",
"styled-components": "^5.3.3"
// etc
},
"resolutions": {
"#mui/styled-engine": "npm:#mui/styled-engine-sc#^5.4.2"
}
// etc
}
However after installing and running the application, the ReactDOM.render() call fails:
Uncaught TypeError: can't access property "button", theme.typography is undefined
node_modules bundle.js:2257
transformedStyleArg createStyled.js:185
Ne flatten.js:80
generateAndInjectStyles ComponentStyle.js:90
S StyledComponent.js:80
O StyledComponent.js:125
O StyledComponent.js:174
The component rendering through react-router is a Login component that uses MUI's button:
import React from 'react'
import Button from '#mui/material/Button'
import { NavigateFunction, useNavigate } from 'react-router-dom'
interface Props {
login: (nav: NavigateFunction) => void
}
export function Login({ login }: Props) {
const navigate = useNavigate()
return (
<Button onClick={() => login(navigate)} variant='contained'>
Login
</Button>
)
}
Figured out the problem. The issue is that I had setup a theme provider from styled-components, but i was passing mainly an empty object.
Setting it up to instead pass a material theme fixes the issue.
import React from 'react'
import { Provider } from 'react-redux'
import { ThemeProvider } from 'styled-components'
import { store } from 'common/store'
import { theme } from './theme'
import { AppRoutes } from './core/AppRoutes'
export function App() {
return (
<Provider store={store}>
<ThemeProvider theme={theme}>
<AppRoutes />
</ThemeProvider>
</Provider>
)
}
// theme.ts
import { createTheme } from '#mui/material'
export const theme = createTheme()

react says Error: Invalid hook call when I try to use material makestyles

I'm new to react and I'm trying to using makestyles and this is how :
in Header.jsx :
import React from "react";
import UseStyles from "./Header_style";
function Header() {
const classes =UseStyles();
return (
<div className={"Main-Header"}>
<div className={"Header-Logo"}>
<div className={classes.test}>test</div>
</div>
</div>
);
};
export default Header;
and style.js :
import {makeStyles} from '#material-ui/styles';
const UseStyles = makeStyles(theme=>({
test: {
backgroundColor: '#BDC3C7',
color :'red !important',
widtH : '18%'
},
}));
export default UseStyles;
but I'm getting folwing 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:
*edit:
This is how I'm using Header :
import React, { Component } from 'react'
import Header from './component/heder/Header.jsx';
class App extends Component {
constructor() {
super();
this.state = {
monsters: [],
searchField: ''
};
}
render() {
return (
<Header/>
);
}
}
export default App;
and another thing, I'm getting following error too :
When you place your Header component in the return or render of a parent component make sure you use <Header /> and not {Header}
additionally if that's not the problem you can check this link which is the official react thread on that error.
Also posting how you render the component that is throwing the error would be very helpful.
Edit* Additionally you don't need to call makeStyles with a function. Since you are not using the theme, you can just call makeStyles with an object like this
const useStyles = makeStyles({
test: {
background: 'white',
width: '100%'
}
});
EDIT and additional answers:
Here's a snippet from MUI's official page on styles:
The way you import makeStyles:
import { makeStyles } from '#material-ui/styles
If you import this way you have to have applied the #material-ui/styles module.
If instead in your package.json you use '#material-ui/core and haven't installed #material-ui/styles you could be getting that error because you don't have the module #material-ui/styles.
If you just have #material-ui/core you can still import makeStyles without installing the standalone #material-ui/styles it is all included in #material-ui/core.
Simply import it like this instead:
import { makeStyles } from '#material-ui/core/styles'
everybody, I've found the solution!
have to use withStyles.

Unit testing Chakra UI with Jest

Currently I am trying to unit test my application that is built with Create-React-App with typescript, and it is styled with chakraui. Chakrui includes a component ThemeProvider that must wrap the entire application as such.
This is my index.tsx file
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { ThemeProvider, CSSReset } from "#chakra-ui/core/dist";
import { theme } from "#chakra-ui/core/dist";
ReactDOM.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CSSReset />
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
For every unit test that I write, I am having to wrap the component with ThemeProvider for the test to pass:
import React from "react";
import { render } from "#testing-library/react";
import { ThemeProvider } from "#chakra-ui/core/dist";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
render(
<ThemeProvider>
<App />
</ThemeProvider>
);
});
});
But this is very verbose, and must be done for every test that I write. Is there a way to do this just once in each .test.tsx file?
You could create your own theme wrapper function
import React from "react";
import { ThemeProvider } from "#chakra-ui/core/dist";
export const ThemeWrapper = ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
);
And then specify the wrapper in the test
import React from "react";
import { render } from "#testing-library/react";
import { ThemeWrapper } from "../testUtils";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
render(<App />, { wrapper: ThemeWrapper });
});
});
This marginally reduces the code for testing. You may be able to also go the route of creating a custom render function (following the steps for redux).
It could look something like
import React from "react";
import { render } from "#testing-library/react";
import { ThemeProvider } from "#chakra-ui/core/dist";
export const renderWithTheme = ui => {
const Wrapper = ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
);
return render(ui, { wrapper: Wrapper });
};
Basically the same as the wrapper above, but more integrated into a test render function. You can adjust the function signature a bit as well if you need to pass in a theme object, or other render options, this is just a simple example.
Now the test looks like
import React from "react";
import { renderWithTheme } from "../testUtils";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
renderWithTheme(<App />);
});
It might be the case that Jest might be mocking your imports from #chakra-ui/core/dist (depending on your jest configuration) which might be resulting your imported chakra-ui components to be undefined.
Importing the Theme Provider and wrapping it everytime with your renders might be one way to do it. The problem might arise when you have multiple components in your index.tsx. So, you might not want to import each and every component.
In that case, you will want to import the actual components from #chakra-ui/core.
The best way (according to me) to do so in Jest is:
jest.mock("#chakra-ui/core", () => {
const ui = jest.requireActual("#chakra-ui/core");
return {
...ui,
customKey: 'customValue',
};
})
This way you can even add custom function and key-values to the imported module.

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

How to handle two configureStore files for Redux in TypeScript (multiple module.exports)?

I'm building a React Native app with TypeScript using Redux for my state.
I like using two seperate files for configureStore. In JS This looks like this:
configureStore.dev.js:
import { applyMiddleware, createStore } from "redux";
import logger from "redux-logger";
import reducers from "../reducers";
const configureStore = () => {
const store = createStore(reducers, applyMiddleware(logger));
return store;
};
export default configureStore;
configureStore.prod.js:
import { createStore } from "redux";
import reducers from "../reducers";
const configureStore = () => {
const store = createStore(reducers);
return store;
};
export default configureStore;
configureStore.js:
import Config from "react-native-config";
if (Config.REACT_ENVIRONMENT === "staging") {
module.exports = require("./configureStore.dev");
} else {
// tslint:disable-next-line no-var-requires
module.exports = require("./configureStore.prod");
}
And then within App.js:
import React, { Component } from "react";
import Orientation from "react-native-orientation";
import { Provider } from "react-redux";
import Navigator from "./navigation/Navigator";
import configureStore from "./redux/store/configureStore";
export const store = configureStore();
export default class App extends Component {
componentDidMount = () => {
Orientation.lockToPortrait();
};
render() {
return (
<Provider store={store}>
<Navigator />;
</Provider>
);
}
}
The problem now with TypeScript is that - after converting these files to .ts and .tsx - this code throws linting errors (and it furthermore blocks all Jest unit tests from running).
The lines where modules.exports exports the respective file depending on environment variables throws the error:
[tslint] require statement not part of an import statement (no-var-requires)
And in App.tsx the import of configureStore throws:
[ts] Module '"/Users/jan/Documents/FullStackFounders/PainButton/painbutton/app/redux/store/configureStore"' has no default export.
How would one handle this case in TypeScript?
The only solution I could come up with was only using one file and using a lot of if's for all the Dev only configs. But that doesn't seem clean to me.
It seems you are mixing import / export and require / module.exports syntax.
Try to use dynamic import expressions.
configureStore.js
export default Config.REACT_ENVIRONMENT === "staging" ? import("./configureStore.dev") : import("./configureStore.prod");
main render file
import configure from "./configureStore.js";
configure.then(configFunc => {
const store = configFunc();
ReactDOM.render(<App store={store} />, document.querySelector("#root"));
})
Pass the store as a prop to the <App /> Component.
I hope it will help.

Resources