passing default theme in material UI4 - reactjs

I'm trying to pass a default theme to makeStyles in material ui 4.
In my component i called my theme, imported of Styled Components, and i called customMaterialStyles and passed to makeStyles;
my root component
import { faTimes } from '#fortawesome/free-solid-svg-icons';
import { Modal } from '#material-ui/core';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Stepper from '#material-ui/core/Stepper';
import { ButtonIcon } from 'commons/ButtonIcon';
import React, { useState } from 'react';
import { useTheme } from 'styled-components';
import { useStyles } from './materialStyles';
import {
StCloseButtonWrapper,
StContainer,
StContentStepper,
StWrapper,
} from './styled';
interface Props {
isVisible: boolean;
onCloseButtonClick: () => void;
}
const CreateEngineSchedule: React.FC<Props> = ({
isVisible,
onCloseButtonClick,
}) => {
const theme = useTheme();
const customMaterialStyles = useStyles(theme.colors);
const [activeStep, setActiveStep] = useState(0);
const steps = ['Nova agenda', 'Escolha a agenda', 'Confirmar'];
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
console.log('steps', steps);
return (
<Modal open={isVisible} className={customMaterialStyles.modal}>
<StContainer>
<StWrapper>
<StCloseButtonWrapper>
<ButtonIcon
icon={faTimes}
onHoverColor='darkBlue'
color='darkGrey'
tooltip
tooltipTitle='FECHAR'
backgroundColor='transparent'
onHoverBackgroundColor='transparent'
onButtonClick={onCloseButtonClick}
/>
</StCloseButtonWrapper>
<StContentStepper>
<Stepper
activeStep={activeStep}
className={customMaterialStyles.stepperRoot}
>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<div>
<button disabled={activeStep === 0} onClick={handleBack}>
Back
</button>
<button onClick={handleNext}>
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
</button>
</div>
</StContentStepper>
</StWrapper>
</StContainer>
</Modal>
);
};
export default CreateEngineSchedule;
Where i'm trying to use the theme of styled components:
import { makeStyles } from '#material-ui/core/styles';
import { DefaultTheme } from 'styled-components';
// import { useTheme } from 'styled-components';
export const useStyles = makeStyles(() => {
// const systemTheme = useTheme();
return {
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
stepperRoot: {
// '& .MuiStep-root:not(.MuiStep-completed)': {
// },
'& .MuiStepConnector-lineHorizontal': {
display: 'none',
},
'& .MuiStepIcon-root': {
fontSize: '3rem',
fill: 'transparent',
border: '1px solid',
borderRadius: '100%',
},
'& .MuiStepIcon-text': {
fill: (props: keyof DefaultTheme['colors']) => props.blue500,
fontSize: '13px',
},
},
};
});
The Error:
I only want use the styled component theme inside the materialUi 4.
you can see the prop here:

I solved this problem typing like this:
fill: (props: DefaultTheme['colors']) => props.blue500,

Related

How to change datagrid rows text color in react-admin?

I want change a text color for row, but can't find decision. I tried this code. "color" not working, but "backgroundColor" working.
export const CustomersList = props => {
const DatagridUsersProps = {
rowStyle: (record, index) => {
return {
color: "#FFCC00",
backgroundColor: (record.is_blocked || record['is_deleted']) && "silver",
};
}
};
return (
<List
{...props}
>
<ScrollingWrapper>
<Datagrid {...DatagridUsersProps}>
<TextField source="id" />
<LinkField source="first_name" />
<LinkField source="last_name" />
<EmailField source="email" />
<PhoneField source="phone" />
</Datagrid>
</ScrollingWrapper>
</List>
);
}
Thank you for any sample code or link to do that!
I believe you can override component class using their makeStyle hooks. Find the component class name using inspect element.
const useStyles = makeStyles({
table: {
backgroundColor: 'Lavender',
},
headerCell: {
backgroundColor: 'MistyRose',
}
cell: {
backgroundColor: 'Pink',
}, });
as per their documentation here https://marmelab.com/react-admin/doc/3.19/Theming.html
you may want to override not only the root component style, but also the style of components inside the root. In this case, the className property isn’t enough. You can take advantage of the classes property to customize the classes that the component uses internally.
hope this helps
For decide this problem i used combination of styled components, useStyles and rowStyle for Datagrid.
StyledEmailField.js:
import {makeStyles} from "#material-ui/core/styles";
import {EmailField} from "react-admin";
import React from "react";
import classnames from 'classnames';
const useStyles = makeStyles({
isDeleted: {
textDecoration: "line-through",
},
});
export const DatagridUsersProps = {
rowStyle: (record, index) => {
return {
backgroundColor: (record.is_blocked) && "silver",
color: (record.is_deleted) && "#616161",
};
}
};
export const StyledEmailField = props => {
const classes = useStyles();
return (
<EmailField
className={classnames({
[classes.isDeleted]: props.record.is_deleted,
})}
{...props}
/>
);
};
UseStales.js:
import React from "react";
import {makeStyles} from "#material-ui/core/styles";
export const stylesForUsersList = makeStyles({
root: {
"& td": {
color: 'inherit',
},
"& table": {
color: 'inherit',
},
"& td a, & td a span": {
color: 'inherit',
},
"& td span": {
color: 'inherit',
}
}
});
List.jsx:
import React from "react";
import { List, Datagrid, TextField, EditButton,usePermissions} from 'react-admin';
import { ScrollingWrapper } from '../../../components/ScrollingWrapper';
import { StyledEmailField } from '../../../components/fields/StyledEmailField';
import { CustomersFilter } from './ListFilters';
import {HideBlockUnblockButtonIfDeleted} from '../../../components/toolbars/BlockUnblockButton';
import DeleteRestoreButtons from '../../../components/toolbars/DeleteRestoreButtons';
import {DatagridUsersProps} from "../../../components/_helpers/props/DatagridProps";
import {stylesForUsersList,
} from "../../../components/_helpers/useStyles";
import {UserRole} from "../../../entities";
const defaultSort = { field: 'id', order: 'DESC' };
export const CustomersList = props => {
const classes = stylesForUsersList()
const { permissions } = usePermissions();
if (!permissions) {
return null;
}
return (
<List
{...props}
sort={defaultSort}
exporter={false}
bulkActionButtons={false}
filters={<CustomersFilter />}
perPage={22}
classes={classes}
>
<ScrollingWrapper>
<Datagrid {...DatagridUsersProps}>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<StyledEmailField source="email" />
<HideBlockUnblockButtonIfDeleted entity={"user"}/>
{(permissions.role === UserRole.admin) &&
<DeleteRestoreButtons/>}
</Datagrid>
</ScrollingWrapper>
</List>
);
}

dark mode toggle in material ui

I am trying to implement a dark toggle in my website. I have gotten the toggle to show up in the correct place. I am using the usestate hook to implement the toggle functionality. However, on clicking the toggle, does not change the theme. I don't understand what is going wrong over here.
Here is the code of the different components.
I have implemented a component for the toggle theme button. Here is the code for togglethemebutton.js
import React, { useState } from "react";
// ----------------------------------------------------------------------
import { Switch } from '#mui/material';
import { createTheme } from '#mui/material/styles';
// ----------------------------------------------------------------------
export default function ToggleThemeButton() {
const [darkState, setDarkState] = useState(false);
const palletType = darkState ? "dark" : "light";
const darkTheme = createTheme({
palette: {
type: palletType,
}
});
const handleThemeChange = () => {
setDarkState(!darkState);
};
return (
<Switch checked={darkState} onChange={handleThemeChange} />
);
}
I am using this component in the Navbar.js component.
import PropTypes from 'prop-types';
// material
import { alpha, styled } from '#mui/material/styles';
import { Box, Stack, AppBar, Toolbar, IconButton } from '#mui/material';
// components
import Iconify from '../../components/Iconify';
//
import Searchbar from './Searchbar';
import AccountPopover from './AccountPopover';
import LanguagePopover from './LanguagePopover';
import NotificationsPopover from './NotificationsPopover';
import ToggleThemeButton from './ToggleThemeButton';
// ----------------------------------------------------------------------
const DRAWER_WIDTH = 280;
const APPBAR_MOBILE = 64;
const APPBAR_DESKTOP = 92;
const RootStyle = styled(AppBar)(({ theme }) => ({
boxShadow: 'none',
backdropFilter: 'blur(6px)',
WebkitBackdropFilter: 'blur(6px)', // Fix on Mobile
backgroundColor: alpha(theme.palette.background.default, 0.72),
[theme.breakpoints.up('lg')]: {
width: `calc(100% - ${DRAWER_WIDTH + 1}px)`
}
}));
const ToolbarStyle = styled(Toolbar)(({ theme }) => ({
minHeight: APPBAR_MOBILE,
[theme.breakpoints.up('lg')]: {
minHeight: APPBAR_DESKTOP,
padding: theme.spacing(0, 5)
}
}));
// ----------------------------------------------------------------------
DashboardNavbar.propTypes = {
onOpenSidebar: PropTypes.func
};
export default function DashboardNavbar({ onOpenSidebar }) {
return (
<RootStyle>
<ToolbarStyle>
<IconButton
onClick={onOpenSidebar}
sx={{ mr: 1, color: 'text.primary', display: { lg: 'none' } }}
>
<Iconify icon="eva:menu-2-fill" />
</IconButton>
<Searchbar />
<Box sx={{ flexGrow: 1 }} />
<Stack direction="row" alignItems="center" spacing={{ xs: 0.5, sm: 1.5 }}>
// here is the toggle button
<ToggleThemeButton/>
<LanguagePopover />
<NotificationsPopover />
<AccountPopover />
</Stack>
</ToolbarStyle>
</RootStyle>
);
}
A posible solution may be to move the logic to change the theme from the ToggleThemButton component to the NavBar and just pass the values needed like this:
ToggleThemeButton:
export default function ToggleThemeButton({handleThemeChange, darkState}) {
return (
<Switch checked={darkState} onChange={handleThemeChange} />
);
}
Then in the NavBar component you could add a theme variable that its updated by the ToggleThemeButton, here i used a new createTheme with the pallete that has a background property just for testing (i don't know much about material ui)
Navbar:
export default function DashboardNavbar({ onOpenSidebar }) {
const [darkState, setDarkState] = useState(false);
const palletType = darkState ? "dark" : "light";
const darkTheme = createTheme({
palette: {
type: palletType,
background: {
default: "#fff"
}
}
});
const [theme, setTheme] = useState(darkTheme);
const handleThemeChange = () => {
setDarkState(!darkState);
setTheme(createTheme({
palette: {
type: palletType,
background: {
default: !darkState? "#000" : "#fff"
}
}
}))
};
return (
<RootStyle theme={theme}>
..... <ToggleThemeButton handleThemeChange={handleThemeChange} darkState={darkState} /> ....
</ToolbarStyle>
</RootStyle>
);
}

How can I create a button to a dialog box inside the PopperComponent of Material UI Labs Autocomplete

I have a Material UI Autocomplete which renders a list of Chips. I have added a button at the bottom of the PopperComponent which when clicked should open a dialog box.
But the Autocomplete doesn't allow the DialogBox to be opened. But the weird part is if I add 'open' to Autocomplete it works.
I have tried adding the onMouseDown event instead of onClick. Also, tried the event.preventDefault(). None of them works. However onMouseDown definitely called my listener for the Dialog box and changed its open state to true, but the dialog box did not appear.
This is the link to the sandbox.
Sandbox to the code
This is the component that implements the Dialog Box.
import React, { useState } from "react";
import { withStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import MuiDialogTitle from "#material-ui/core/DialogTitle";
import MuiDialogContent from "#material-ui/core/DialogContent";
import MuiDialogActions from "#material-ui/core/DialogActions";
import IconButton from "#material-ui/core/IconButton";
import CloseIcon from "#material-ui/icons/Close";
import Typography from "#material-ui/core/Typography";
import { orange } from "#material-ui/core/colors";
const styles = theme => ({
form: {
display: "flex",
flexDirection: "column",
margin: "auto",
width: "fit-content"
},
formControl: {
marginTop: theme.spacing(2),
minWidth: 120
},
formControlLabel: {
marginTop: theme.spacing(1)
},
closeButton: {
position: "absolute",
right: theme.spacing(1),
top: theme.spacing(1),
color: theme.palette.grey[500]
},
selectEmpty: {
marginTop: theme.spacing(2)
},
floatingLabelFocusStyle: {
color: "green"
},
separator: {
marginTop: theme.spacing(1)
},
menuStyle: {
border: "1px solid black",
borderRadius: "5%",
backgroundColor: "lightgrey"
}
});
const DialogTitle = withStyles(styles)(props => {
const { children, classes, onClose, ...other } = props;
return (
<MuiDialogTitle disableTypography className={classes.root} {...other}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton
aria-label="close"
className={classes.closeButton}
onClick={onClose}
>
<CloseIcon />
</IconButton>
) : null}{" "}
</MuiDialogTitle>
);
});
const DialogContent = withStyles(theme => ({
root: {
padding: theme.spacing(2)
}
}))(MuiDialogContent);
const DialogActions = withStyles(theme => ({
root: {
margin: 0,
padding: theme.spacing(1)
}
}))(MuiDialogActions);
const ActionButton = withStyles(theme => ({
root: {
color: "#E87424",
backgroundColor: "white",
"&:hover": {
backgroundColor: orange[100]
}
}
}))(Button);
const ManageTagButton = withStyles(theme => ({
root: {
color: "#E87424"
}
}))(Button);
const TagContainer = props => {
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
console.log("Dialog box clicked");
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<ManageTagButton
onMouseDown={event => {
event.preventDefault();
handleClickOpen();
}}
size="small"
>
MANAGE TAGS
</ManageTagButton>
<Dialog
fullWidth
maxWidth={"sm"}
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title">Manage Tags</DialogTitle>
<DialogContent dividers>
<h1>
This is the component of the dialog box. In reality I neeed to
display a data table with CRUD operations to add more tags.
</h1>
</DialogContent>
<DialogActions>
<ActionButton autoFocus onClick={handleClose} color="secondary">
CLOSE
</ActionButton>
</DialogActions>
</Dialog>
</div>
);
};
export default TagContainer;
This is the component that implements the Autocomplete.
import React, { Fragment } from "react";
import Chip from "#material-ui/core/Chip";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { withStyles } from "#material-ui/core/styles";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import ListItemSecondaryAction from "#material-ui/core/ListItemSecondaryAction";
import Paper from "#material-ui/core/Paper";
import TagContainer from "./TagContainer";
const ListItemCustom = withStyles(theme => ({
gutters: {
paddingLeft: 0,
paddingRight: 0
},
secondaryAction: {
paddingRight: 0
}
}))(ListItem);
const AutocompleteCustom = withStyles(theme => ({
endAdornment: {
display: "none"
}
}))(Autocomplete);
const CreateButton = withStyles(theme => ({
root: {
color: "#E87424"
}
}))(Button);
const MuiFilledInputCustom = makeStyles(
{
underline: {
"&&&:before": {
borderBottom: "none"
},
"&&:after": {
borderBottom: "none"
}
}
},
{ name: "MuiFilledInput" }
);
const loadCustomStyles = () => {
MuiFilledInputCustom();
};
export default function AddTagToThread() {
loadCustomStyles();
const handleSubmit = () => {
console.log("Add tags to thread");
};
const useStyles = makeStyles({
root: {
minWidth: 300,
width: 300,
height: 250,
minHeight: 250,
zIndex: 1
},
buttons: {
display: "flex",
justifyContent: "flex-end"
}
});
const PaperComponentCustom = options => {
const classes = useStyles();
const { containerProps, children } = options;
return (
<Paper className={classes.root} {...containerProps} square>
{children}
<div className={classes.buttons}>
<TagContainer />
</div>
</Paper>
);
};
return (
<List dense={false}>
<ListItemCustom>
<ListItemText>
<AutocompleteCustom
multiple
id="size-small-filled-multi"
size="medium"
options={tagList}
noOptionsText="No options"
freeSolo
filterSelectedOptions
PaperComponent={PaperComponentCustom}
getOptionLabel={option => option.name}
onChange={(event, value) => {
console.log(value);
}}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="default"
style={{
backgroundColor: option.color
}}
label={option.name}
size="medium"
{...getTagProps({ index })}
/>
))
}
renderOption={option => (
<Fragment>
<Chip
variant="default"
style={{
backgroundColor: option.color,
padding: "15px",
marginLeft: "12px"
}}
label={option.name}
size="medium"
/>
</Fragment>
)}
renderInput={params => (
<TextField
{...params}
variant="filled"
label="Filter By Tag"
placeholder="Select Tag"
/>
)}
/>
</ListItemText>
<ListItemSecondaryAction>
<CreateButton onClick={handleSubmit}>ADD TAG</CreateButton>
</ListItemSecondaryAction>
</ListItemCustom>
</List>
);
}
const tagList = [
{ name: "Follow Up", tagId: 1, color: "#FFC107" },
{ name: "Important", tagId: 2, color: "#46B978" },
{ name: "Idea", tagId: 3, color: "#EEA5F6" },
{ name: "Non Issue", tagId: 4, color: "#2EACE2" }
];
I have been stuck at this for last couple of days. Any help is greatly appreciated.
The issue with your code is that the <Dialog/> component is in the PaperComponentCustom component which gets unmounted after the option is selected.
<Paper className={classes.root} {...containerProps} square>
{children}
<ManageTagButton onMouseDown={handleClickOpen} fullWidth>
MANAGE TAGS
</ManageTagButton>
</Paper>
The solution to keep only the <ManageTagButton/> component in the PaperComponentCustom and move the <Dialog/> component one level up. I imagine that even if you have 10 elements in the <List/> you would still have only one <Dialog>, you cannot have 10 dialog components opened at once.
So therefore your <AddTagToThread/> component should render the dialog directly and the state of the dialog open and the handlers handleOpen and handleClose should be moved in the <AddTagToThread/> component also
Working codesandbox HERE, code below
Autocomplete component
import React, { Fragment, useState } from "react";
import Chip from "#material-ui/core/Chip";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { withStyles } from "#material-ui/core/styles";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import ListItemSecondaryAction from "#material-ui/core/ListItemSecondaryAction";
import Paper from "#material-ui/core/Paper";
import TagContainer from "./TagContainer";
const ListItemCustom = withStyles(theme => ({
gutters: {
paddingLeft: 0,
paddingRight: 0
},
secondaryAction: {
paddingRight: 0
}
}))(ListItem);
const AutocompleteCustom = withStyles(theme => ({
endAdornment: {
display: "none"
}
}))(Autocomplete);
const CreateButton = withStyles(theme => ({
root: {
color: "#E87424"
}
}))(Button);
const MuiFilledInputCustom = makeStyles(
{
underline: {
"&&&:before": {
borderBottom: "none"
},
"&&:after": {
borderBottom: "none"
}
}
},
{ name: "MuiFilledInput" }
);
const loadCustomStyles = () => {
MuiFilledInputCustom();
};
const ManageTagButton = withStyles(theme => ({
root: {
color: "#E87424"
}
}))(Button);
export default function AddTagToThread() {
loadCustomStyles();
const handleSubmit = () => {
console.log("Add tags to thread");
};
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
console.log("Dialog box clicked");
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const useStyles = makeStyles({
root: {
minWidth: 300,
width: 300,
height: 250,
minHeight: 250,
zIndex: 1
},
buttons: {
display: "flex",
justifyContent: "flex-end"
}
});
const PaperComponentCustom = options => {
const classes = useStyles();
const { containerProps, children } = options;
return (
<Paper className={classes.root} {...containerProps} square>
{children}
<ManageTagButton onMouseDown={handleClickOpen} fullWidth>
MANAGE TAGS
</ManageTagButton>
</Paper>
);
};
return (
<>
<TagContainer open={open} handleClose={handleClose} />
<List dense={false}>
<ListItemCustom>
<ListItemText>
<AutocompleteCustom
multiple
id="size-small-filled-multi"
size="medium"
options={tagList}
noOptionsText="No options"
freeSolo
filterSelectedOptions
PaperComponent={PaperComponentCustom}
getOptionLabel={option => option.name}
onChange={(event, value) => {
console.log(value);
}}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="default"
style={{
backgroundColor: option.color
}}
label={option.name}
size="medium"
{...getTagProps({ index })}
/>
))
}
renderOption={option => (
<Fragment>
<Chip
variant="default"
style={{
backgroundColor: option.color,
padding: "15px",
marginLeft: "12px"
}}
label={option.name}
size="medium"
/>
</Fragment>
)}
renderInput={params => (
<TextField
{...params}
variant="filled"
label="Filter By Tag"
placeholder="Select Tag"
/>
)}
/>
</ListItemText>
<ListItemSecondaryAction>
<CreateButton onClick={handleSubmit}>ADD TAG</CreateButton>
</ListItemSecondaryAction>
</ListItemCustom>
</List>
</>
);
}
const tagList = [
{ name: "Follow Up", tagId: 1, color: "#FFC107" },
{ name: "Important", tagId: 2, color: "#46B978" },
{ name: "Idea", tagId: 3, color: "#EEA5F6" },
{ name: "Non Issue", tagId: 4, color: "#2EACE2" }
];
Dialog component
import React, { useState } from "react";
import { withStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import MuiDialogTitle from "#material-ui/core/DialogTitle";
import MuiDialogContent from "#material-ui/core/DialogContent";
import MuiDialogActions from "#material-ui/core/DialogActions";
import IconButton from "#material-ui/core/IconButton";
import CloseIcon from "#material-ui/icons/Close";
import Typography from "#material-ui/core/Typography";
import { orange } from "#material-ui/core/colors";
const styles = theme => ({
form: {
display: "flex",
flexDirection: "column",
margin: "auto",
width: "fit-content"
},
formControl: {
marginTop: theme.spacing(2),
minWidth: 120
},
formControlLabel: {
marginTop: theme.spacing(1)
},
closeButton: {
position: "absolute",
right: theme.spacing(1),
top: theme.spacing(1),
color: theme.palette.grey[500]
},
selectEmpty: {
marginTop: theme.spacing(2)
},
floatingLabelFocusStyle: {
color: "green"
},
separator: {
marginTop: theme.spacing(1)
},
menuStyle: {
border: "1px solid black",
borderRadius: "5%",
backgroundColor: "lightgrey"
}
});
const DialogTitle = withStyles(styles)(props => {
const { children, classes, onClose, ...other } = props;
return (
<MuiDialogTitle disableTypography className={classes.root} {...other}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton
aria-label="close"
className={classes.closeButton}
onClick={onClose}
>
<CloseIcon />
</IconButton>
) : null}{" "}
</MuiDialogTitle>
);
});
const DialogContent = withStyles(theme => ({
root: {
padding: theme.spacing(2)
}
}))(MuiDialogContent);
const DialogActions = withStyles(theme => ({
root: {
margin: 0,
padding: theme.spacing(1)
}
}))(MuiDialogActions);
const ActionButton = withStyles(theme => ({
root: {
color: "#E87424",
backgroundColor: "white",
"&:hover": {
backgroundColor: orange[100]
}
}
}))(Button);
const TagContainer = ({ open, handleClose }) => {
return (
<Dialog
fullWidth
maxWidth={"sm"}
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title">Manage Tags</DialogTitle>
<DialogContent dividers>
<h1>
This is the component of the dialog box. In reality I neeed to display
a data table with CRUD operations to add more tags.
</h1>
</DialogContent>
<DialogActions>
<ActionButton autoFocus onClick={handleClose} color="secondary">
CLOSE
</ActionButton>
</DialogActions>
</Dialog>
);
};
export default TagContainer;

React: Slider with custom hook not working properly

I am trying to create a custom hook with a slider ui element. My goal is to be able to access the slider value from the parent element so as to update some other ui parts.
However, it seems that the slider values do not update correctly: when the user tries to drag the slider tooltip it only moves one step. It seems like the mouse events stop being tracked after useEffect gets called.
What can I do to fix this and have a smooth dragging behaviour?
Here is my code (sandbox):
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './demo';
ReactDOM.render(<Demo />, document.querySelector('#root'));
demo.js
import React, { useEffect } from "react";
import useSlider from "./slider";
function CustomizedSlider() {
const [CustomSlider, sliderValue] = useSlider("Slider", 50);
useEffect(() => {
console.log("Slider value: " + sliderValue);
}, [sliderValue]);
return <CustomSlider />;
}
export default CustomizedSlider;
slider.js
import React, { useState } from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({...
}));
const PrettoSlider = withStyles({...
})(Slider);
export default function useSlider(label, defaultState) {
const classes = useStyles();
const [state, setState] = useState(defaultState);
const CustomSlider = () => {
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={state}
onChange={(event, v) => {
setState(v);
}}
/>
</Paper>
);
};
return [CustomSlider, state];
}
Thanks a lot for your help!
The issue is that your CustomSlider is a new component type for each render due to it being a unique function each time. This causes it to unmount/remount with each render rather than just re-render which will cause all sorts of issues (as you've seen).
Rather than a custom hook, I think you really just want a custom component. Below is one way you could structure it with only minimal changes to your initial code.
demo.js
import React, { useEffect } from "react";
import CustomSlider from "./CustomSlider";
function CustomizedSlider() {
const [value, setValue] = React.useState(50);
useEffect(() => {
console.log("Slider value: " + value);
}, [value]);
return <CustomSlider label="Slider" value={value} setValue={setValue} />;
}
export default CustomizedSlider;
CustomSlider.js
import React from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
root: {
width: 300 + 24 * 2,
padding: 24
},
margin: {
height: theme.spacing(1)
}
}));
const PrettoSlider = withStyles({
root: {
color: "#a2df77",
height: 8
},
thumb: {
height: 24,
width: 24,
backgroundColor: "#fff",
border: "2px solid currentColor",
marginTop: -8,
marginLeft: -12,
"&:focus,&:hover,&$active": {
boxShadow: "inherit"
}
},
active: {},
valueLabel: {
left: "calc(-50% + 4px)"
},
track: {
height: 8,
borderRadius: 4
},
rail: {
height: 8,
borderRadius: 4
}
})(Slider);
const CustomSlider = ({ label, value, setValue }) => {
const classes = useStyles();
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={value}
onChange={(event, v) => {
setValue(v);
}}
/>
</Paper>
);
};
export default CustomSlider;

How to use 'theme' and 'props' in makeStyles?

How do I write makeStyles() so that it allows me to use both theme variables and props?
I've tried this:
const useStyles = makeStyles(theme => ({
appbar: props => ({
boxShadow: "none",
background: "transparent",
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
color: theme.palette.getContrastText(props)
}),
}));
And called it in the component with:
const classes = useStyles(backgroundColor);
Where backgroundColor is a prop on the component with type CSSProperties["backgroundColor"]
But I'm getting the error:
TypeError: Cannot read property 'rules' of undefined
at RuleList.onUpdate (C:\Users\...\node_modules\jss\dist\jss.cjs.js:944:14)
at RuleList.update (C:\Users\...\node_modules\jss\dist\jss.cjs.js:923:12)
at StyleSheet.update (C:\Users\...\node_modules\jss\dist\jss.cjs.js:1178:39)
at attach (C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:141:18)
at C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:262:7
at useSynchronousEffect (C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:207:14)
at C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:254:5
at Layout (C:\Users\...\.next\server\static\development\pages\index.js:1698:17)
at processChild (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2888:14)
at resolve (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2812:5)
at ReactDOMServerRenderer.render (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3202:22)
at ReactDOMServerRenderer.read (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3161:29)
at renderToString (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3646:27)
at render (C:\Users\...\node_modules\next-server\dist\server\render.js:86:16)
at renderPage (C:\Users\...\node_modules\next-server\dist\server\render.js:211:20)
at ctx.renderPage (C:\Users\...\.next\server\static\development\pages\_document.js:2404:22)
100 | handleSignUpClick,
101 | backgroundColor
102 | }) => {
> 103 | const classes = useStyles(backgroundColor);
104 | return (
105 | <AppBar className={classes.appbar}>
106 | <Container maxWidth="lg">
edit: I'm using version 4.0.0-beta.1 of material core and styles
Tested with:
"#material-ui/core": "^4.0.0-beta.1",
"#material-ui/styles": "^4.0.0-rc.0",
JavaScript example:
my-component.js
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import { useStyles } from './my-component.styles.js';
const myComponent = (props) => {
const styleProps = { width: '100px', height: '100px' };
const classes = useStyles(styleProps);
return (
<div className={classes.mySelector}></div> // with 100px and height 100px will be applied
)
}
my-component.styles.js
export const useStyles = makeStyles(theme => ({
mySelector: props => ({
display: 'block',
width: props.width,
height: props.height,
}),
}));
TypeScript example:
my-component.ts
import React, { FC } from 'react';
import { makeStyles } from '#material-ui/styles';
import { useStyles } from './my-component.styles.ts';
import { MyComponentProps, StylesProps } from './my-component.interfaces.ts';
const myComponent: FC<MyComponentProps> = (props) => {
const styleProps: StylesProps = { width: '100px', height: '100px' };
const classes = useStyles(styleProps);
return (
<div className={classes.mySelector}></div> // with 100px and height 100px will be applied
)
}
my-component.interfaces.ts
export interface StyleProps {
width: string;
height: string;
}
export interface MyComponentProps {
}
my-component.styles.ts
import { Theme } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import { StyleProps } from './my-components.interfaces.ts';
export const useStyles = makeStyles<Theme, StyleProps>((theme: Theme) => ({
mySelector: props => ({ // props = { width: string; height: string }
display: 'block',
width: props.width,
height: props.height,
}),
}));
Update
Re-tested with
"#material-ui/core": "^4.12.X"
You need to pass an object to useStyles rather than a string.
So instead of:
const classes = useStyles(backgroundColor);
you should have:
const classes = useStyles(props);
or
const classes = useStyles({backgroundColor});
Then you can get at backgroundColor using:
color: theme.palette.getContrastText(props.backgroundColor).
Here's a working example:
https://codesandbox.io/s/o7xryjnmly
You can do this: (i dont know if is the best way but works... also can access to the theme ang globals provider (using themeProvider))
import { makeStyles }from '#material-ui/core/styles'
import styles from './styles';
const useStyles = makeStyles(theme => (styles(theme))); // here call styles function imported from styles.js
const SideNav = ({drawerState, toggleDrawer}) => {
const classes = useStyles();
return (
<Box className={classes.root}>
<Drawer className="drawer" anchor="left" open={drawerState} onClose={() => toggleDrawer(false)}>
<NavList></NavList>
</Drawer>
</Box>
);
export default SideNav;
and in styles.js
const styles = (theme) => {
return ({
root: {
'& .drawer': {
backgroundColor:'red'
}
}
});
}
export default styles;
makeStyles get the theme by params
so you can create a styles.js for every component with a arrow function inside and
calling from makeStyles that can access to the theme provider.
We have 2 modes in general, your prop variable is imported to the component or not.
If your prop variable is imported, it is a global variable, so it is valid in makeStyles():
import {prop} from ...
const useStyles = makeStyles((theme) => ({
className:{
// use prop
}
})
define component...
If your prop variable is defined in the component (such as a state), you have 2 choices:
You can pass the prop variable to makeStyles():
const useStyles = makeStyles((theme,prop) => ({
className:{
// use prop
}
})
define component...
You can use arrow function without passing any argument (except theme) to makeStyles():
const useStyles = makeStyles((theme) => ({
className:(prop)=>({
//use prop
})
})
define component...
Here is an example of how you can use only props or props and theme both with makeStyles() just like styled-components
component.js
import { tableCellStyling } from './component.styled.js';
const RenderRow = (props) => {
const { noPaddingTopBottom } = tableCellStyling(props);
return(
<StyledTableRow>
{data.map( (row,i) => (
<StyledTableCell className={noPaddingTopBottom}>
{row.data}
</StyledTableCell>
)}
</StyledTableRow>
)
};
Assuming my props object which is being passed by RenderRow Component to tableCellStyling has { color: 'grey', thinRow: true } in it
component.styled.js
import { makeStyles } from '#material-ui/core/styles';
export const tableCellStyling = makeStyles(theme => ({
noPaddingTopBottom: {
borderBottom: ({ color }) => color ? `2px solid ${color}` : '2px solid red',
paddingBottom: props => props.hasActions && 0,
paddingTop: props => props.hasActions && 0,
backgroundColor: theme.palette.common.white,
},
}));
The way you can do it is like this:
import { makeStyles, createStyles, Theme } from "#material-ui/core/styles";
...
...
const classes = useStyles();
...
...
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: propName => ({
border: "none",
boxShadow: "none",
cursor: propName ? "pointer" : "auto",
width: "100%",
backgroundColor: "#fff",
padding: "15px 15px"
}),
updated: {
marginTop: 12,
fontWeight: 400,
color: "#939393"
}
})
);
You can use the prop name inside the element you are styling by making it an arrow function that returns the styling. In addition, you can also style other elements inside the createStyles hook. This took me some time, I hope anyone finds it useful. ✨🔥

Resources