I am confused when setting styling for Material-UI based front-end.
Where does theme come from in the example code below and what are the effects of the theme?
import React from "react";
import Container from "#material-ui/core/Container";
const useStyles = makeStyles(theme => ({
root: {
height: "100%"
}
}));
const Sample = props => {
const classes = useStyles();
return (
<Container className={classes.root}/>
);
}
There is a provider of theme so called ThemeProvider
document of material-ui theming
more customized usage advanced theming
<ThemeProvider theme={outerTheme}>
<Checkbox defaultChecked />
<ThemeProvider theme={innerTheme}>
<Checkbox defaultChecked />
</ThemeProvider>
</ThemeProvider>
It provide the theme to the child components, we usually define this at the top level of the project.
Then you can access the Theme defined above via multiple ways
accessing-the-theme-in-a-component
For example, for classical component we have withTheme HOC
import { withTheme } from '#material-ui/core/styles';
function DeepChildRaw(props) {
return <span>{`spacing ${props.theme.spacing}`}</span>;
}
const DeepChild = withTheme(DeepChildRaw);
For functional component, we have useTheme hooks
import { useTheme } from '#material-ui/core/styles';
function DeepChild() {
const theme = useTheme();
return <span>{`spacing ${theme.spacing}`}</span>;
}
And you can use them inside makeStyles and createStyles as normal
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(theme => ({
root: {
width: theme.spacing(1);
}
}));
import { createStyles } from '#material-ui/core/styles';
const styles = (theme: Theme) => createStyles({
root: {
width: theme.spacing(1);
}
})
Related
Ive downloaded a DarkModeToggle npm for my react app however I am a bit confused as to actually add the functionallity. Currently the button lets me click it on the appbar and the button itself changes however the state of my app does not.
import React, { useState, useEffect } from "react";
import { Container, AppBar, Typography, Grow, Grid } from "#material-ui/core";
import { useDispatch } from "react-redux";
import DarkModeToggle from "react-dark-mode-toggle";
// import { getPosts } from './actions/posts'
import Posts from "./components/Posts/Posts";
import Form from "./components/Form/Form";
import wisp_logo from "./images/wisp_logo.png";
import useStyles from "./styles";
const App = () => {
const [currentId, setCurrentId] = useState();
const classes = useStyles();
const dispatch = useDispatch();
const [isDarkMode, setIsDarkMode] = useState(() => false);
return (
<Container maxwidth="lg">
<AppBar className={classes.appBar} position="static" color="inherit">
<DarkModeToggle
onChange={setIsDarkMode}
checked={isDarkMode}
size={80}
/>
</AppBar>
</Container>
);
};
export default App;
If you wish to customize the theme, you need to use the ThemeProvider component in order to inject a theme into your application. Here's a simple example:
Custom variables:
const darkTheme = createTheme({
palette: {
type: "dark"
}
});
Use the ThemeProvider component:
<ThemeProvider theme={darkTheme}>
<AppBar
position="static"
color={`${isDarkMode ? "default" : "primary"}`}
>
<DarkModeToggle
onChange={setIsDarkMode}
checked={isDarkMode}
size={80}
/>
</AppBar>
</ThemeProvider>
Using a ternary to change the theme:
color={`${isDarkMode ? "default" : "primary"}`}
Exemplo completo aqui: https://codesandbox.io/s/holy-wood-cdgpks?file=/src/App.js
I believe there are 2 things to change:
The first one is that "isDarkMode" is a boolean, and inside the useState you're using a function, so must be like this:
const [isDarkMode, setIsDarkMode] = useState(false);
Also, when sending the "setIsDarkMode" function you need to update your state, like this:
<DarkModeToggle
onChange={() => setIsDarkMode(prevState => !prevState)}
checked={isDarkMode}
size={80}
/>
So "onChange" is now a function that is going to update the state on every click
I have created a theme in the index of my React.JS project using MUI. When I try to apply my style to my Appbar the theme does not correctly modify the menu button nor the menu itself. the button looks generic default and the menu remains white when it should match the color of the Appbar itself.
My index.tsx looks as such:
import React from "react";
import ReactDOM from "react-dom";
import AppbarTop from "./AppbarTop";
import { Router } from "react-router";
import { createBrowserHistory } from "history";
import AdapterDateFns from "#mui/lab/AdapterDateFns";
import { LocalizationProvider } from "#mui/lab";
import { createTheme } from "#mui/material";
import { ThemeProvider } from "#mui/styles";
import { StyledEngineProvider } from "#mui/material/styles";
const customHistory = createBrowserHistory();
const theme = createTheme({
palette: {
primary: {
main: "#242526"
},
secondary: {
main: "#d975d0"
},
text: {
primary: "#E4E6EB",
secondary: "#B0B3B8"
},
background: {
default: "#242526",
paper: "#242526"
}
}
});
ReactDOM.render(
<React.StrictMode>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Router history={customHistory}>
<ThemeProvider theme={theme}>
<StyledEngineProvider injectFirst>
<AppbarTop />
</StyledEngineProvider>
</ThemeProvider>
</Router>
</LocalizationProvider>
</React.StrictMode>,
document.getElementById("root")
);
My appbar.tsx looks like this:
import React from "react";
import {
AppBar,
Box,
Button,
Container,
Menu,
MenuItem,
Toolbar
} from "#mui/material";
import HomeIcon from "#mui/icons-material/Home";
import { makeStyles } from "#mui/styles";
const useStyles = makeStyles((theme?: any) => ({
appBar: {
background: theme.palette.primary.main,
height: "60px",
position: "relative"
}
}));
const AppbarTop: React.FC<{ [key: string]: any }> = () => {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState<any>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<AppBar position="static" className={classes.appBar}>
<Toolbar>
<Button
id="basic-button"
aria-controls="basic-menu"
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
onClick={handleClick}
>
Dashboard
</Button>
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button"
}}
>
<MenuItem onClick={handleClose}>
<HomeIcon />{" "}
</MenuItem>
</Menu>
{/*test speed dial*/}
<Container maxWidth="sm"></Container>
<Box></Box>
</Toolbar>
</AppBar>
</>
);
};
export default AppbarTop;
Can someone please explain what I am missing?
Change this line:
import { ThemeProvider } from "#mui/styles";
To:
import { ThemeProvider } from "#mui/material/styles";
Reason: There are 2 ThemeProviders here
The one from #mui/styles: This ThemeProvider does send the Theme object down via context, it works fine, you can still access it using the useTheme hook:
const theme = useTheme();
return <Box sx={{ width: 10, height: 10, bgcolor: theme.palette.primary.main }} />
The one from #mui/material/styles: This ThemeProvider is a wrapper of the above, but it also injects the theme to the StyledEngineThemeContext.Provider, which allows you to access the theme when using MUI API (sx prop/styled()). The problem here is that the Button and Menu components uses the styled() API under-the-hood so the ThemeProvider needs to be imported from #mui/material/styles to make it work.
return <Box sx={{ width: 10, height: 10, bgcolor: 'primary.main' }} />
Related answers
Difference between #mui/material/styles and #mui/styles?
Cannot use palette colors from MUI theme
MUI - makeStyles - Cannot read properties of undefined
Material UI Dark Mode
I recently built a components library but when I import my components my IDE doesn't show autocomplete props like other components do (based on propTypes) :
In my library, all components are exported with a HOC and I found out that by removing the HOC the props are now available in auto complete. Here's my HOC :
import * as React from 'react'
import hoistNonReactStatics from 'hoist-non-react-statics'
import PropTypes from 'prop-types'
import { defaultTheme } from '../styles/defaultTheme'
const withTheme = (WrappedComponent) => {
const ThemeContext = React.createContext(defaultTheme)
const Enhance = (props) => {
const { _reactThemeProviderForwardedRef, ...rest } = props
return (
<ThemeContext.Consumer>
{(theme) => (
<WrappedComponent
{...rest}
theme={theme}
ref={_reactThemeProviderForwardedRef}
/>
)}
</ThemeContext.Consumer>
)
}
Enhance.propTypes = {
_reactThemeProviderForwardedRef: PropTypes.any,
...WrappedComponent.propTypes,
}
const ResultComponent = React.forwardRef((props, ref) => (
<Enhance {...props} _reactThemeProviderForwardedRef={ref} />
))
ResultComponent.displayName = `withTheme(${
WrappedComponent.displayName || WrappedComponent.name
})`
return hoistNonReactStatics(ResultComponent, WrappedComponent)
}
export default withTheme
I'm probably doing something wrong in my withTheme but I cannot find out what.
Here is one of the components :
import React from 'react'
import { Text, StyleSheet, TextPropTypes } from 'react-native'
import PropTypes from 'prop-types'
import { buildStyleSheet } from '../util/style-helpers'
import { withTheme } from '../core/theming'
const CustomText = React.forwardRef((props, ref) => {
const { children, className = '', style, theme, ...others } = props
const customStyle = buildStyleSheet(className, 'text', theme)
return (
<Text
ref={ref}
style={StyleSheet.flatten([customStyle, style])}
{...others}
>
{children}
</Text>
)
})
const propTypes = {
children: PropTypes.any,
className: PropTypes.string,
theme: PropTypes.object.isRequired,
...TextPropTypes,
}
CustomText.displayName = 'CustomText'
CustomText.propTypes = propTypes
export default withTheme(CustomText)
I am using material UI with Next.js. I am running onnpm run dev. My problem is that the styling on the site completely breaks whenever I press the reloading button on the browser. Is this normal behavior? Seems like Material-UI stops working.
Here is my code.
I have an index.js and a component.
index
import React, { Component } from 'react'
import CssBaseline from '#material-ui/core/CssBaseline';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import AppBar from '../components/NavBar/NavBar';
const theme = createMuiTheme({
palette: {
primary: {
main: '#f28411',
},
},
});
class App extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<React.Fragment>
<CssBaseline />
<AppBar />
</React.Fragment>
</MuiThemeProvider>
)
}
}
export default App
component
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar'
import Toolbar from '#material-ui/core/Toolbar'
import Typography from '#material-ui/core/Typography'
class NavBar extends Component {
render() {
const { classes } = this.props;
return(
<div>
<AppBar position="static">
<Toolbar>
<Typography variant="title" color="inherit">
Test
</Typography>
</Toolbar>
</AppBar>
</div>
);
}
}
const styles = theme => ({
title: {
color: '#FFF',
},
});
NavBar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(NavBar);
I had this same issue but was not using styled-components so the above answer did not apply, so I thought I would post in case this helps anyone else. To fix this, I just had to add the _document.js file under the pages/ directory included in the material-ui sample:
import React from 'react'
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheets } from '#material-ui/core/styles'
import theme from '../shared/utils/theme'
export default class MyDocument extends Document {
render() {
return (
<Html lang='en'>
<Head>
<meta name='theme-color' content={theme.palette.primary.main} />
<link
rel='stylesheet'
href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets()
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
}
}
It could be possible that you need a babel plugin for this.
In your package, add
npm install --save-dev babel-plugin-styled-components
In your .babelrc, add
{
"plugins": [
[ "styled-components", { "ssr": true, "displayName": true, "preprocess": false } ]
]
}
Let me know if this works.
As it turns out, you cannot use all of the third-party libraries for Next JS in the same way as you would use for React apps. You need to modify your pages/_document.js and pages/_app.js. Also, you will need theme.js for configuring Material UI colors and other default styles. You can include theme.js to any folder, in my case it is in helpers folder.
_app.js
import React from "react";
import App from "next/app";
import theme from '../helpers/theme'; // needed for Material UI
import CssBaseline from '#material-ui/core/CssBaseline';
import {ThemeProvider} from '#material-ui/core/styles';
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
//Anything returned here can be accessed by the client
return {pageProps: pageProps};
}
render() {
const {Component, pageProps, router} = this.props;
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* default by next js */}
<Component {...pageProps}/>
</ThemeProvider>
)
}
}
export default MyApp
_document.js
import Document, {Html, Head, Main, NextScript} from "next/document";
import theme from '../helpers/theme';
import { ServerStyleSheets } from '#material-ui/core/styles';
import React from "react";
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
let props = {...initialProps};
return props;
}
render() {
return (
<Html>
<Head>
<meta name="theme-color" content={theme.palette.primary.main} />
</Head>
<body>
<Main/>
<NextScript/>
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and register rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
theme.js
import { createMuiTheme } from '#material-ui/core/styles';
import { red } from '#material-ui/core/colors';
// Create a theme instance.
const theme = createMuiTheme({
palette: {
primary: {
main: '#FFF212',
},
secondary: {
main: '#FFF212',
},
error: {
main: red.A400,
},
background: {
default: '#fff',
},
},
});
export default theme;
There is an amazing repository in Github https://github.com/vercel/next.js/tree/canary/examples
There you can find a list of libraries with the correct way of using them in Next JS apps.
For your case here is the link: https://github.com/vercel/next.js/tree/canary/examples/with-material-ui.
I have the following component, that is build with https://material-ui-next.com/.
import React from 'react';
import { AppBar, Toolbar } from 'material-ui';
import { Typography } from 'material-ui';
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles';
import {lightBlue} from 'material-ui/colors';
const theme = createMuiTheme({
palette: {
primary: {
main:lightBlue['A700']
},
text: {
primary: '#fff',
}
},
});
const View = (props) => (
<MuiThemeProvider theme={theme}>
<AppBar position="static">
<Toolbar>
<Typography variant="title">
{props.title}
</Typography>
</Toolbar>
</AppBar>
</MuiThemeProvider>
);
export default View;
I am trying to write a test for it:
import React from 'react';
import { shallow } from 'enzyme';
import View from '../Views/View';
import { Typography } from 'material-ui';
it('renders', () => {
const wrapper = shallow(<View title='Welcome' />);
expect(wrapper.find('Typography').text()).toEqual('Welcome');
});
How to write a test for a component, that is using material-ui components? In the case above, I tried to figure out, if the component contains Welcome.
I read https://material-ui-next.com/guides/testing/, but it is not clear, how can I write a test.
UPD: API changed from shallow to mount
Did you tried to use their API described here?
Probably your test can look something like this:
import React from 'react';
import { createMount } from 'material-ui/test-utils';
import View from '../Views/View';
import { Typography } from 'material-ui';
it('renders', () => {
const mount = createMount();
const wrapper = mount(<View title='Welcome' />);
expect(wrapper.find('Typography').text()).toEqual('Welcome');
});