access the theme from outside material-ui component - reactjs

I have a theme provider using the standard dark theme. I'd like to be able to access the details of this theme from my own custom component, but I cannot figure out how that would be done. In the example below, this.props.theme is undefined
ReactDOM.render(
<MuiThemeProvider theme={theme}>
<App/>
</MuiThemeProvider>, document.getElementById('root')
);
class App extends Component {
render() {
const {classes} = this.props;
return (
<div className="App">
<MainMenu/>
<div className={classes.root}>
<Grid container spacing={8}>
<Grid item xs>
<Chart theme={this.props.theme}/>
</Grid>
</Grid>
</div>
</div>
);
}
}

You can also use the useTheme hook!
import { useTheme } from '#material-ui/styles';
function DeepChild() {
const theme = useTheme();
return <span>{`spacing ${theme.spacing}`}</span>;
}
https://material-ui.com/styles/advanced/#accessing-the-theme-in-a-component

You need to use the HOC (Higher Order Component) provided with material-ui (note I am using the beta version, YMMV).
Example:
LeftNavigation.jsx
import React from 'react';
import PropTypes from 'prop-types';
import Hidden from 'material-ui/Hidden';
import Drawer from 'material-ui/Drawer';
import List from 'material-ui/List';
import Divider from 'material-ui/Divider';
import { withStyles } from 'material-ui/styles';
import { MailFolderListItems, OtherMailFolderListItems } from '../../../components/tileData';
const styles = theme => ({
docked: {
height: '100%',
},
drawerPaper: {
width: 250,
height: '100%',
[theme.breakpoints.up('md')]: {
width: theme.drawerWidth,
position: 'relative',
height: '100%',
},
},
drawerHeader: theme.mixins.toolbar,
});
class LeftNavigation extends React.Component {
render() {
const { classes, theme } = this.props;
const drawer = (
<div>
<div className={classes.drawerHeader} />
<Divider />
<List><MailFolderListItems toggle={this.props.handleDrawerToggle} /></List>
<Divider />
<List><OtherMailFolderListItems toggle={this.props.handleDrawerToggle} /></List>
</div>
);
return [
<Hidden mdUp key="mobile">
<Drawer
type="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={this.props.mobileOpen}
classes={{ paper: classes.drawerPaper }}
onRequestClose={this.props.handleDrawerToggle}
ModalProps={{ keepMounted: true /* Better open performance on mobile. */ }}
>
{drawer}
</Drawer>
</Hidden>,
<Hidden mdDown implementation="css" key="desktop">
<Drawer
type="permanent"
open
classes={{
docked: classes.docked,
paper: classes.drawerPaper,
}}
>
{drawer}
</Drawer>
</Hidden>,
];
}
}
LeftNavigation.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
mobileOpen: PropTypes.bool.isRequired,
handleDrawerToggle: PropTypes.func.isRequired
};
export default withStyles(styles, { withTheme: true })(LeftNavigation);
The const styles = theme => ({...}) is where you define your styles.
The export default withStyles(styles, { withTheme: true })(LeftNavigation); shows the usage with the higher order component to pass the styles / theme down to your component.
I use withTheme: true so that not only my styles, but the theme is passed as well.
So for your code, I would do the following:
import { withStyles } from 'material-ui/styles';
const styles = theme => ({
root: {
height: '100%'
}
})
class App extends Component {
render() {
const {classes} = this.props;
return (
<div className="App">
<MainMenu/>
<div className={classes.root}>
<Grid container spacing={8}>
<Grid item xs>
<Chart theme={this.props.theme}/>
</Grid>
</Grid>
</div>
</div>
);
}
}
export default withStyles(styles, { withTheme: true})(App);

And for people using Typesctipt and class components, you'll also need to add:
export interface MyComponentNameProps extends WithStyles<typeof styles, true> {
// your props here...
theme: Theme
}

Related

Unable to access object elements with props and Route in SidebarItem Component

I am trying to access the object elements from appRoutes file via props. I made an appRoutes File from where I want to access all elements.
appRoutes File:
import HomePage from '../pages/home/HomePage';
import DashboardPageLayout from '../pages/dashboard/DashboardPageLayout'
import DashboardOutlinedIcon from '#mui/icons-material/DashboardOutlined';
import FormatListBulletedOutlinedIcon from '#mui/icons-material/FormatListBulletedOutlined';
import ChangeLog from "../pages/changlog/ChangeLog"
import DefaultPage from "../pages/dashboard/DefaultPage"
import SaasPage from "../pages/dashboard/SaasPage"
import AnalyticsPage from "../pages/dashboard/AnalyticsPage"
import DashboardIndex from '../pages/dashboard/DashboardIndex'
const appRoutes=[
{
index:true,
element:<HomePage/>,
state: "home"
},
{
path:"/dashboard",
element:<DashboardPageLayout/>,
state:"dashboard",
sidebarProps:{
displayText:"Dashboard",
icon: <DashboardOutlinedIcon />
},
child:[
{
index: true,
element:<DashboardIndex/>,
State:"dashboard.index",
},
{
path:"/dashboard/default",
element:<DefaultPage />,
state:"dashboard.default",
sidebarProps:{
displayText:"Default"
},
},
{
path: "/dashboard/analytics",
element:<AnalyticsPage/>,
state: "dashboard.analytics",
sidebarProps:{
displayText:"AnalyticsPage"
},
},
{
path:"/dashboard/saas",
element:<SaasPage/>,
state:"dashboard.saas",
sidebarProps:{
displayText:"SaasPage"
},
},
]
},
{
path: "/changelog",
element:<ChangeLog/>,
state:"changelog",
sidebarProps:{
displayText:"ChangeLog",
icon: <FormatListBulletedOutlinedIcon />
},
}
];
export default appRoutes;
I am trying to map all elements in my Sidebar file with route variable and passed in SidebarItem component with prop item.
sidebar.jsx:
import { Avatar, Drawer, List, Toolbar } from "#mui/material";
import { Stack } from "#mui/system";
import assets from "../../assets";
import colorConfigs from "../../configs/colorConfigs";
import appRoutes from "../../routes/appRoutes"
import sizeConfigs from "../../configs/sizeConfigs";
import SidebarItem from "./SidebarItem";
const Sidebar = () => {
return (<Drawer variant="permanent" sx={{
width: sizeConfigs.sidebar.width,
flexShrink: 0,
"& .MuiDrawer-paper": {
width: sizeConfigs.sidebar.width,
boxSizing: "border-box",
borderRight: "0px",
backgroundColor: colorConfigs.sidebar.bg,
color: colorConfigs.sidebar.color
}
}}>
<List disablePadding>
<Toolbar sx={{ marginBottom: "20px" }}>
<Stack sx={{
width: "100%"
}} direction="row" justifyContent="center">
<Avatar src={assets.images.logo}/>
</Stack>
</Toolbar>
{appRoutes.map((route,index)=>(
route.sidebarProps ?(
<SidebarItem item = {route} key={index} />
) : null
)
)}
</List>
</Drawer>);
};
export default Sidebar;
in SidebarItem component I have defined item prop which is passed in sidebarItem.jsx.
SidebarItem.jsx:
import React from 'react'
import { ListItemButton,ListItemIcon } from '#mui/material'
import colorConfigs from '../../configs/colorConfigs'
const SidebarItem = ({item}) => {
return (
item.sidebarPropps && item.path ?
(
<ListItemButton>
<ListItemIcon sx={{color:colorConfigs.sidebar.color}}>
{item.sidebarProps.icon && item.sidebarProps.icon}
</ListItemIcon>
{item.sidebarProps.displayText}
</ListItemButton>
):null
)
}
export default SidebarItem
But this item prop is unable to access route elements from Sidebar.jsx file and does not displaying element from appRoutes.jsx file.
Please help to identify why my elements are not accessible from item props?

MUI v5 cant' change primary color of any components [duplicate]

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

TypeError: Cannot read properties of undefined (reading 'breakpoints') in material ui [duplicate]

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

get child state in stateless component using hooks

README:
Hi! I'm new to using React, and even newer to using hooks so please correct me if my verbiage is incorrect. In fact, I was struggling to even google my issue/come up with a title for this post - how would I best put this problem into words?
Question :
I have a root component which contains a table in its state, and I'm using Material UI and react-csv to create a NavBar with a save button that can save the table. Material UI makes use of hooks; I know if my NavBar component was stateful I could write data={this.props.table} to get the table, but I was wondering how I would download the table given the current framework? Is it possible?
CodePen: https://codesandbox.io/embed/old-dust-88mrp
Root Component:
import React from "react";
import ReactDOM from "react-dom";
import NavBar from "./NavBar";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
table: "this is a table"
};
}
render() {
return (
<div>
<NavBar />
<div>{this.state.table}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
NavBar:
[I tried to simplify code as much as possible!]
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
import ListItemText from "#material-ui/core/ListItemText";
import SaveIcon from "#material-ui/icons/Save";
import Tooltip from "#material-ui/core/Tooltip";
import { CSVLink } from "react-csv";
const StyledMenu = withStyles({
paper: {
border: "1px solid #d3d4d5"
}
})(props => (
<Menu
elevation={0}
getContentAnchorEl={null}
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
transformOrigin={{
vertical: "top",
horizontal: "center"
}}
{...props}
/>
));
const StyledMenuItem = withStyles(theme => ({
root: {
"&:focus": {
backgroundColor: theme.palette.primary.main,
"& .MuiListItemIcon-root, & .MuiListItemText-primary": {
color: theme.palette.common.white
}
}
}
}))(MenuItem);
export default function PrimarySearchAppBar() {
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = event => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<AppBar position="static">
<Toolbar>
<div>
<Tooltip disableFocusListener title="Save">
<IconButton size="medium" onClick={handleClick} color="inherit">
<SaveIcon />
</IconButton>
</Tooltip>
<StyledMenu
id="customized-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<StyledMenuItem>
{/* In stateful components I could put this.props.table here,
but how does this translate to a stateless component? */}
<CSVLink data={"this is a test"}>
<ListItemText primary="Data" />
</CSVLink>
</StyledMenuItem>
</StyledMenu>
</div>
</Toolbar>
</AppBar>
</div>
);
}
Thanks for any advice/help!!
<NavBar table={this.state.table}/>
export default function PrimarySearchAppBar({table}) {
<CSVLink data={table}>
}

Reference to theme's primary color instead of a specific color in MUI

Using ReactJS and MUI, I have a project in which I have changed the theme colors.
const newTheme = getMuiTheme({
fontFamily: 'Roboto, sans-serif',
palette: {
primary1Color: cyan500,
primary2Color: cyan700,
primary3Color: grey400,
accent1Color: amberA400,
accent2Color: grey100,
accent3Color: grey500,
textColor: darkBlack,
alternateTextColor: white,
canvasColor: white,
borderColor: grey300,
disabledColor: fade(darkBlack, 0.3),
pickerHeaderColor: cyan500,
clockCircleColor: fade(darkBlack, 0.07),
shadowColor: fullBlack,
},
});
// App class
render() {
return(
<ThemeProvider theme={newTheme}>
<Project />
</ThemeProvider>
)
}
Everything works as expected. Certain components, like buttons have the ability to set the color based on the primary prop. However, I have a component that uses an icon that needs the primary color. I can import the color and set it directly:
import React from 'react';
import ActionLock from 'material-ui/svg-icons/action/lock';
import {cyan500} from 'material-ui/styles/colors';
export default class LockIcon extends React.Component {
render() {
return(
<ActionLock color={cyan500} />
)
}
}
Is there some way to reference the theme's primary color, rather than setting the color in each component individually? Something like:
import React from 'react';
import ActionLock from 'material-ui/svg-icons/action/lock';
import {primary1Color} from 'somewhere';
export default class LockIcon extends React.Component {
render() {
return(
<ActionLock color={primary1Color} />
)
}
}
If you're using React v16.8.0 and Material-UI v3.5.0 or greater, you can utilize the useTheme() hook:
import { useTheme } from '#material-ui/core/styles';
function LockIcon = () => {
const theme = useTheme();
return (
<ActionLock color={theme.palette.primary1Color} />
}
Yes you have!
using muiThemeable..
import muiThemeable from 'material-ui/styles/muiThemeable';
class LockIcon extends React.Component {
render() {
return(
<ActionLock color={this.props.muiTheme.palette.primary1Color} />
)
}
}
export default muiThemeable()(LockIcon)
from material-ui docs
Adding how to access theme colors in material-ui v1.0.0 (currently beta) Using withTheme component.
Also check the following Example.
import React, {Component} from 'react';
import { withTheme } from 'material-ui/styles';
class WithThemeExample extends Component {
render() {
const { theme } = props;
const {primary, secondary} = theme.palette.text;
return (
<div>
<div style={{color: primary}}>Hi in Primary color</div>
<div style={{color: secondary}}>Bye in Secondary color</div>
</div>
);
}
}
export default withTheme()(WithThemeExample);
If you're using system properties, you can define a string path of the Palette object to the color value like below:
<Box sx={{ color: "primary.main" }}>primary.main</Box>
<Box sx={{ color: "secondary.main" }}>secondary.main</Box>
<Box sx={{ color: "error.main" }}>error.main</Box>
<Box sx={{ color: "warning.main" }}>warning.main</Box>
<Box sx={{ color: "info.main" }}>info.main</Box>
<Box sx={{ color: "success.main" }}>success.main</Box>
<Box sx={{ color: "text.primary" }}>text.primary</Box>
<Box sx={{ color: "text.secondary" }}>text.secondary</Box>
<Box sx={{ color: "text.disabled" }}>text.disabled</Box>
The above is the same as:
<Box sx={{ color: theme => theme.palette.primary.main }}>primary.main</Box>
<Box sx={{ color: theme => theme.palette.secondary.main }}>secondary.main</Box>
<Box sx={{ color: theme => theme.palette.error.main }}>error.main</Box>
<Box sx={{ color: theme => theme.palette.warning.main }}>warning.main</Box>
<Box sx={{ color: theme => theme.palette.info.main }}>info.main</Box>
<Box sx={{ color: theme => theme.palette.success.main }}>success.main</Box>
<Box sx={{ color: theme => theme.palette.text.primary }}>text.primary</Box>
<Box sx={{ color: theme => theme.palette.text.secondary }}>text.secondary</Box>
<Box sx={{ color: theme => theme.palette.text.disabled }}>text.disabled</Box>
The example using the callback is more verbose but is type-safe, the shorter one with only string is quicker and good when prototyping, but you may want to store the string in a constant to prevent any typo errors and help the IDE refactor your code better.
Live Demo
Ok if you are using material-ui version greater than 4, then the above solution might not work for you. Follow the below's code
import { withTheme } from '#material-ui/core/styles';
function DeepChildRaw(props) {
return <span>{`spacing ${props.theme.spacing}`}</span>;
}
const DeepChild = withTheme(DeepChildRaw);
Reference: https://material-ui.com/styles/advanced/
With react hooks, now you don't need to warp component in withTheme, just use useTheme:
import { useTheme } from '#material-ui/core/styles';
export default function MyComponent() {
const theme = useTheme();
return <span>{`spacing ${theme.spacing}`}</span>;
}
MUI V5
For the users of MUI v5, you can specify a function inside the sx css style directly, ex:
<Box sx={{ color: (theme) => theme.palette.primary1Color }} />
You can use the useTheme() hook and access the colors like the example below.
There are also some other color variants as well.
Mui 5:
import * as React from 'react';
import PropTypes from 'prop-types';
import { NavLink } from "react-router-dom";
import { useTheme } from "#mui/material/styles";
function VinNavLink(props) {
const theme = useTheme();
return (
<NavLink {...props} style={({ isActive }) => isActive ? { textDecoration: "underline", color: theme.palette.primary.main} : undefined}>{props.children}</NavLink>
);
}
export default VinNavLink;

Resources