I am having an issue trying to use MUI Styled () on bigger scale: can someone take a look at the code we used to use in prior versions and let me know how to replicate it in MUI V5.
Old way:
const useStyles = makeStyles((theme) => ({
root: {
backgroundColor: "#fdfdff",
},
pageHeader: {
padding: theme.spacing(4),
display: "flex",
marginBottom: theme.spacing,
},
pageIcon: {
display: "inline-block",
padding: theme.spacing(2),
color: "#3c44b1",
},
pageTitle: {
paddingLeft: theme.spacing(4),
"& .MuiTypography-subtitle2": {
opacity: "0.6",
},
},
}));
export default function PageHeader(props) {
const classes = useStyles();
const { title, subTitle, icon } = props;
return (
<Paper elevation={0} square className={classes.root}>
<div className={classes.pageHeader}>
<Card className={classes.pageIcon}>{icon}</Card>
<div className={classes.pageTitle}>
<Typography variant="h6" component="div">
{title}
</Typography>
<Typography variant="subtitle2" component="div">
{subTitle}
</Typography>
</div>
</div>
</Paper>
);
}
My attempt to accomplish the same in MUI V5 is not working properly. it renders but it doesn't look the same and it's all over the place.
const rootStyle = styled("div")({
backgroundColor: "#fdfdff",
});
const headerStyle = styled("div")(({ theme }) => ({
padding: theme.spacing(4),
display: "flex",
marginBottom: theme.spacing,
}));
const iconStyle = styled("div")(({ theme }) => ({
display: "inline-block",
padding: theme.spacing(2),
color: "#3c44b1",
}));
const titleStyle = styled("div")(({ theme }) => ({
paddingLeft: theme.spacing(4),
"& .MuiTypography-subtitle2": {
opacity: "0.6",
},
}));
export default function PageHeader(props) {
const { title, subTitle, icon } = props;
return (
<rootStyle>
<Paper elevation={0} square>
<headerStyle>
<iconStyle>
<Card>{icon}</Card>
</iconStyle>
<titleStyle>
<Typography variant="h6" component="div">
{title}
</Typography>
<Typography variant="subtitle2" component="div">
{subTitle}
</Typography>
</titleStyle>
</headerStyle>
</Paper>
</rootStyle>
);
}
I am new to MUI and there are not a lot of examples out there that cover this. I truly appreciate your help!
Below is a v5 version of your code with the same look as the v4 version. I added default values for the props just for demonstration purposes.
You had two main issues:
You added additional div layers for the styling rather than styling the elements that originally received the styles (e.g. Paper, Card).
You assigned the styled divs to variable names that start with a lowercase letter which caused them to be rendered as DOM tags rather than components (so the styling would have been completely ignored).
From https://reactjs.org/docs/components-and-props.html#rendering-a-component:
React treats components starting with lowercase letters as DOM tags.
import Paper from "#mui/material/Paper";
import Card from "#mui/material/Card";
import Typography from "#mui/material/Typography";
import { styled } from "#mui/material/styles";
import PersonIcon from "#mui/icons-material/Person";
const StyledPaper = styled(Paper)({
backgroundColor: "#fdfdff"
});
const HeaderDiv = styled("div")(({ theme }) => ({
padding: theme.spacing(4),
display: "flex",
marginBottom: theme.spacing
}));
const StyledCard = styled(Card)(({ theme }) => ({
display: "inline-block",
padding: theme.spacing(2),
color: "#3c44b1"
}));
const TitleDiv = styled("div")(({ theme }) => ({
paddingLeft: theme.spacing(4),
"& .MuiTypography-subtitle2": {
opacity: "0.6"
}
}));
export default function PageHeader(props) {
const {
title = "Title",
subTitle = "sub-title",
icon = <PersonIcon />
} = props;
return (
<StyledPaper elevation={0} square>
<HeaderDiv>
<StyledCard>{icon}</StyledCard>
<TitleDiv>
<Typography variant="h6" component="div">
{title}
</Typography>
<Typography variant="subtitle2" component="div">
{subTitle}
</Typography>
</TitleDiv>
</HeaderDiv>
</StyledPaper>
);
}
An alternative (and much more concise) way to convert the v4 code to v5 is to use the sx prop:
import Paper from "#mui/material/Paper";
import Card from "#mui/material/Card";
import Typography from "#mui/material/Typography";
import PersonIcon from "#mui/icons-material/Person";
import Box from "#mui/material/Box";
export default function PageHeader(props) {
const {
title = "Title",
subTitle = "sub-title",
icon = <PersonIcon />
} = props;
return (
<Paper elevation={0} square sx={{ bgcolor: "#fdfdff" }}>
<Box sx={{ p: 4, display: "flex", mb: 1 }}>
<Card sx={{ display: "inline-block", p: 2, color: "#3c44b1" }}>
{icon}
</Card>
<Box sx={{ pl: 4, "& .MuiTypography-subtitle2": { opacity: 0.6 } }}>
<Typography variant="h6" component="div">
{title}
</Typography>
<Typography variant="subtitle2" component="div">
{subTitle}
</Typography>
</Box>
</Box>
</Paper>
);
}
Here is one more option using a single styled call, though in my opinion this would be more brittle to maintain than the other options:
import Paper from "#mui/material/Paper";
import Card from "#mui/material/Card";
import Typography from "#mui/material/Typography";
import { styled } from "#mui/material/styles";
import PersonIcon from "#mui/icons-material/Person";
const StyledPaper = styled(Paper)(({ theme }) => ({
backgroundColor: "#fdfdff",
"& > div": {
padding: theme.spacing(4),
display: "flex",
marginBottom: theme.spacing(1),
"& .MuiCard-root": {
display: "inline-block",
padding: theme.spacing(2),
color: "#3c44b1"
},
"& > div": {
paddingLeft: theme.spacing(4),
"& .MuiTypography-subtitle2": {
opacity: "0.6"
}
}
}
}));
export default function PageHeader(props) {
const {
title = "Title",
subTitle = "sub-title",
icon = <PersonIcon />
} = props;
return (
<StyledPaper elevation={0} square>
<div>
<Card>{icon}</Card>
<div>
<Typography variant="h6" component="div">
{title}
</Typography>
<Typography variant="subtitle2" component="div">
{subTitle}
</Typography>
</div>
</div>
</StyledPaper>
);
}
Related
I have the following component:
import * as React from "react";
import { createTheme, ThemeProvider } from '#mui/material/styles'
import { Box } from '#mui/system';
import { InputBase, TextField, Typography } from "#mui/material";
import ReactQuill from 'react-quill';
import { NoEncryption } from "#mui/icons-material";
type Props = {
issueId: string
}
export default function DialogBox({ issueId }: Props) {
const myTheme = createTheme({
// Set up your custom MUI theme here
})
const [newMsg, setNewMsg] = React.useState("");
const [startNewMsg, setStartNewMsg] = React.useState(false)
const handleNewMsgInput = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
}
const handleKeyPress = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "Escape") {
// setStartNewMsg(false);
setNewMsg((prev) => "");
}
}
return (
<Box flexDirection="column" sx={{ display: "flex", alignItems: "center", backgroundColor: "lightblue", height: "100%", gap: "2rem" }} onKeyPress={(e) => {
handleKeyPress(e)
}}>
<Typography
sx={{
width: "fit-content",
height: "fit-content",
fontFamily: "Amatic SC",
background: "lightblue",
fontSize: "3rem"
}}>
Message board
</Typography>
{startNewMsg ?
<Box sx={{ width: "fit-content", height: "fit-content" }}>
<ReactQuill style={{ backgroundColor: "white", height: "10rem", maxWidth: "30rem", maxHeight: "10rem" }} theme="snow" />
</Box>
:
<TextField id="filled-basic" label="write new message" sx={{ "& fieldset": { border: 'none' }, backgroundColor: "white", borderRadius: "5px" }} variant="filled" fullWidth={true} onClick={(e) => setStartNewMsg((prev) => true)} onChange={(e) => handleNewMsgInput(e)} />}
</Box >
)
}
Which's causing me the following issue of text appearing out of my white textbox:
I notice on inspection the following property which's responsible for the problem:
My question is, what would be the best way to manipulating the values of that element? How should I retrieve them?
Regards!
Use ch units (the size of the text characters) and adjust the size based on the input.
Example:
const Test = () => {
const [text, setText] = useState('');
return (
<div>
<input
value={text}
onChange= {(e) => setText(e.target.value)}
style= {{height: `${text.length}ch`, width: `${text.length}ch`}}
/>
</div>
);
};
^ that is an input box that will grow based on the size of the text.
You don't want:
height: 100%;
as this will only allow the height to be as big as its parent container.
I want to move the content from Form.jsx according to the direction in which the drawer will move. When I click on the menu icon, the Drawer will be expanded but the content inside the body cannot be moved according to the drawer. How to make it enable here?
Before the drawer menu gets clicked:
After the drawer menu gets clicked:
Code is below:
Form.jsx
import { styled } from '#mui/material/styles';
import Box from '#mui/material/Box';
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
}));
export default function Form() {
return (
<Box component="main" sx={{ flexGrow: 1, p: 9 }}>
<DrawerHeader />
<TextField id="outlined-basic" label="Outlined" variant="outlined" />
<TextField id="filled-basic" label="Filled" variant="filled" />
<TextField id="standard-basic" label="Standard" variant="standard" />
</Box>
);
};
Drawer.jsx
import * as React from 'react';
import { styled, createTheme, ThemeProvider } from '#mui/material/styles';
import Box from '#mui/material/Box';
import MuiDrawer from '#mui/material/Drawer';
import MuiAppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import List from '#mui/material/List';
import CssBaseline from '#mui/material/CssBaseline';
import Typography from '#mui/material/Typography';
import Divider from '#mui/material/Divider';
import IconButton from '#mui/material/IconButton';
import MenuIcon from '#mui/icons-material/Menu';
import ChevronLeftIcon from '#mui/icons-material/ChevronLeft';
import ChevronRightIcon from '#mui/icons-material/ChevronRight';
import ListItem from '#mui/material/ListItem';
import ListItemButton from '#mui/material/ListItemButton';
import ListItemIcon from '#mui/material/ListItemIcon';
import ListItemText from '#mui/material/ListItemText';
import InboxIcon from '#mui/icons-material/MoveToInbox';
import MailIcon from '#mui/icons-material/Mail';
import { Link } from 'react-router-dom';
const drawerWidth = 240;
const openedMixin = (theme) => ({
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
});
const closedMixin = (theme) => ({
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: `calc(${theme.spacing(7)} + 1px)`,
[theme.breakpoints.up('sm')]: {
width: `calc(${theme.spacing(8)} + 1px)`,
},
});
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
}));
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})(({ theme, open }) => ({
zIndex: theme.zIndex.drawer + 1,
background: "fb8500",
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
({ theme, open }) => ({
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
boxSizing: 'border-box',
...(open && {
...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme),
}),
...(!open && {
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme),
}),
}),
);
export default function MiniDrawer() {
const PageList = [
{
id: 1,
text: "Add new Donee",
to: "/form",
},
{
id: 2,
text: "Donees",
to: "/view",
},
];
// const theme = useTheme();
const theme = createTheme({
palette: {
primary: {
// Purple and green play nicely together.
main: '#ffca3a',
},
secondary: {
// This is green.A700 as hex.
main: '#11cb5f',
},
},
});
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<ThemeProvider theme={theme}>
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar position="fixed" open={open}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
sx={{
marginRight: 5,
...(open && { display: 'none' }),
}}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Compassion Lanka Data Entry
</Typography>
</Toolbar>
</AppBar>
<Drawer variant="permanent" open={open}>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</DrawerHeader>
<Divider />
<List>
{PageList.map((text, index) => (
<ListItem key={text.id} disablePadding sx={{ display: 'block' }}>
<ListItemButton
sx={{
minHeight: 48,
justifyContent: open ? 'initial' : 'center',
px: 2.5,
}}
component={Link} to={text.to}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: open ? 3 : 'auto',
justifyContent: 'center',
}}
>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text.text} sx={{ opacity: open ? 1 : 0 }} />
</ListItemButton>
</ListItem>
))}
</List>
<Divider />
</Drawer>
</Box>
</ThemeProvider>
);
}
Set margin or padding of <Box component="main"> to the width of the drawer.
I have mui v5 dialog that I am not able to set its width using style() component.
import {
Dialog,
DialogContent,
DialogTitle,
Paper,
Typography,
} from "#mui/material";
import { Close } from "#mui/icons-material";
import { styled } from "#mui/material/styles";
import ActionButton from "./ActionButton";
import React from "react";
const StyledDialog = styled(Dialog)(({ theme }) => ({
fullWidth: true, // it's not taking effect
maxWidth: "lg", // it's not taking effect
padding: theme.spacing(2),
position: "absolute",
top: theme.spacing(5),
"& MuiDialog-paper": {
padding: theme.spacing(2),
position: "absolute",
top: theme.spacing(5),
},
"& .MuiTypography-h6": {
paddingRight: "0px",
},
}));
export default function Popup(props) {
const { title, children, openPopup, setOpenPopup } = props;
return (
<StyledDialog open={openPopup} onClose={() => setOpenPopup(false)}>
<DialogTitle>
<div style={{ display: "flex" }}>
<Typography variant="h6" component="div" style={{ flexGrow: 1 }}>
{title}
</Typography>
<ActionButton
color="secondary"
onClick={() => setOpenPopup(false)}
>
<Close />
</ActionButton>
</div>
</DialogTitle>
<DialogContent dividers>{children}</DialogContent>
</StyledDialog>
However, if I listed the props directly inside the it works.
<StyledDialog fullWidth="true" maxWidth="lg">
</StyledDialog>
What is the correct way of setting up the width and maxWidth.
Try this on the styledDialog declaration:
const StyledDialog = styled((props) => (
<Dialog
fullWidth={true}
maxWidth={'lg'}
{...props}
/>
))(({ theme }) => ({
// Your code to style the dialog goes here
}));
The problem on your code is that you arent passing the properties fullWidth and maxWidth to the component.
You are trying to use maxWidth: lg and fullWidth: true like css, but they are only for the Dialog API.
So you could use maxWidth in the component as an API like
<Dialog maxWidth="lg" fullWidth ... >
or you can add it as css style using theme.
const StyledDialog = styled(Dialog)(({ theme }) => ({
maxWidth: theme.breakpoints.values.lg, // there's no styling such fullWidth
...
}));
You could have a look at the default theme.
I am new to Next js, I want to call the news api in useEffect and dispatch the news array to my store. then filter it based on the user's input in the search bar in the header. problem is once the use effect data fetching is done and user starts typing rather than filtering the news array the screen gets re-render and the data fetching starts again. how to prevent this re-rendering and save the news array?
I tried to use getStaticprops but useDispatch is not allowed in there.
index.js
import { fetchNews } from "../store/actions/newsActions";
import NewsInfo from "../components/NewsInfo";
import { useDispatch, useSelector } from "react-redux";
import CircularProgress from "#material-ui/core/CircularProgress";
export default function Home() {
const dispatch = useDispatch();
const { news } = useSelector((state) => state.news);
React.useEffect(() => {
dispatch(fetchNews());
}, []);
return (
<>
{/* this wrapper cmp will make each headline uniquely accessible */}
{news?.articles ? (
news.articles.map((article) => (
<React.Fragment key={article.publishedAt}>
<NewsInfo headlines={article} />
</React.Fragment>
))
) : (
<CircularProgress />
)}
</>
);
}
Header.js
import React from "react";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import Typography from "#material-ui/core/Typography";
import InputBase from "#material-ui/core/InputBase";
import { fade, makeStyles } from "#material-ui/core/styles";
import MenuIcon from "#material-ui/icons/Menu";
import SearchIcon from "#material-ui/icons/Search";
import { useDispatch } from "react-redux";
import { filterHeadlines } from "../store/actions/newsActions";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
display: "none",
[theme.breakpoints.up("sm")]: {
display: "block",
},
},
search: {
position: "relative",
borderRadius: theme.shape.borderRadius,
backgroundColor: fade(theme.palette.common.white, 0.15),
"&:hover": {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
marginLeft: 0,
width: "100%",
[theme.breakpoints.up("sm")]: {
marginLeft: theme.spacing(1),
width: "auto",
},
},
searchIcon: {
padding: theme.spacing(0, 2),
height: "100%",
position: "absolute",
pointerEvents: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
inputRoot: {
color: "inherit",
},
inputInput: {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create("width"),
width: "100%",
[theme.breakpoints.up("sm")]: {
width: "12ch",
"&:focus": {
width: "20ch",
},
},
},
}));
function SearchAppBar() {
const classes = useStyles();
const dispatch = useDispatch();
const [input, setInput] = React.useState("");
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="open drawer"
>
<MenuIcon />
</IconButton>
<Typography className={classes.title} variant="h6" noWrap>
Material-UI
</Typography>
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ "aria-label": "search" }}
value={input}
onChange={(e) => setInput(e.target.value)}
onBlur={() => dispatch(filterHeadlines(input))}
/>
</div>
</Toolbar>
</AppBar>
</div>
);
}
export default SearchAppBar;
you can use store dispatch to getServersideProps, getstaticprops
from your store file
export const rootDispatch = store.dispatch;
i have tried to center it using flexbox justify-content, but it didn't work
Then I tried by replacing the Typography component with a div tag, still, it didn't work
import {AppBar,Zoom,Toolbar,Typography,CssBaseline,useScrollTrigger,Fab,makeStyles,IconButton,Container } from '#material-ui/core'
import PropTypes from 'prop-types';
import KeyboardArrowUpIcon from '#material-ui/icons/KeyboardArrowUp';
import styles from './menu.module.css'
import MenuIcon from '#material-ui/icons/Menu'
const useStyles = makeStyles(theme => ({
root: {
position: "fixed",
bottom: theme.spacing(2),
right: theme.spacing(2)
}
}));
function ScrollTop(props) {
const { children, window } = props;
const classes = useStyles();
// Note that you normally won't need to set the window ref as useScrollTrigger
// will default to window.
// This is only being set here because the demo is in an iframe.
const trigger = useScrollTrigger({
target: window ? window() : undefined,
disableHysteresis: true,
threshold: 100
});
const handleClick = event => {
const anchor = (event.target.ownerDocument || document).querySelector(
"#back-to-top-anchor"
);
if (anchor) {
anchor.scrollIntoView({ behavior: "smooth", block: "center" });
}
};
return (
<Zoom in={trigger}>
<div onClick={handleClick} role="presentation" className={classes.root}>
{children}
</div>
</Zoom>
);
}
// ScrollTop.propTypes = {
// children: PropTypes.element.isRequired,
// /**
// * Injected by the documentation to work in an iframe.
// * You won't need it on your project.
// */
// window: PropTypes.func
// };
export default function BackToTop(props) {
return (
<React.Fragment>
<CssBaseline />
<AppBar>
<Toolbar>
<IconButton>
<MenuIcon
className={styles.icon}
edge="end"
color="inherit"
aria-label="menu"
/>
</IconButton>
<Typography align='center' variant="h5">
Información
</Typography>
</Toolbar>
</AppBar>
<Toolbar id="back-to-top-anchor" />
<Container>
<Typography></Typography>
</Container>
<ScrollTop {...props}>
<Fab color="secondary" size="small" aria-label="scroll back to top">
<KeyboardArrowUpIcon />
</Fab>
</ScrollTop>
</React.Fragment>
);
}}```
The problem is probably Toolbar does not center its children. You could do something like
const useStyles = makeStyles(theme => ({
root: {
position: "fixed",
bottom: theme.spacing(2),
right: theme.spacing(2)
},
toolBar: {
display: "flex",
justifyContent: "center",
alignItems: "center",
}
}));
and use it
<Toolbar className={classes.toolBar}>
<IconButton>
<MenuIcon
className={styles.icon}
edge="end"
color="inherit"
aria-label="menu"
/>
</IconButton>
<Typography align='center' variant="h5">
Información
</Typography>
</Toolbar>
To make sure the typography is centered all the time, you can wrap it with the component. Because Container always centers it's children, the typography will be centered as a result of that. Like this
<Container>
<Typography variant="h5">Información</Typography>
</Container>