i want to know how can i use React.memo, useMemo and useCallback correct way.
I know if dependency or props have object and array, it doesnt work properly.
So I'm trying to apply it to my project as small as possible because I have a lot of object and array type props and dependency
// email is props
const onRequestOTP: () => void = useCallback(async () => {
const res = await resendEmail(email);
if (res?.data?.status === 1) {
setIsResponseError(false);
setIsRequest(true);
setStep(2);
} else if (res?.data?.status === 0) {
setIsResponseError(true);
}
}, [email]);
import styled from 'styled-components';
import { memo } from 'react';
// lib
import { selectpalette } from 'lib/styles/palette';
// components
import Font from 'components/atoms/Font';
// #mui
import { Grid } from '#mui/material';
import MdiIcon from 'components/atoms/Icon';
const Alert: ({ text, isError }: Props) => JSX.Element = ({ text, isError }: Props) => {
const bgColor = isError ? 'red_op_12' : 'primary_op_12';
return (
<Wrapper bgColor={selectpalette(bgColor)}>
<Grid
container
alignItems="center"
direction="row"
flexWrap="nowrap"
justifyContent="space-between"
>
<Grid item sx={{ paddingRight: '8px' }}>
{isError ? (
<MdiIcon width={20} height={20} src="/icons/warning/ic_warning_red_round.svg" />
) : (
<MdiIcon width={20} height={20} src="/icons/mail/ic_mail_primary.svg" />
)}
</Grid>
<Grid item flex="1 1 auto">
<Font className="body-1" color={isError ? 'error' : 'primary'}>
{text}
</Font>
</Grid>
</Grid>
</Wrapper>
);
};
const Wrapper = styled.div<{ bgColor: string }>`
padding: 16px;
border-radius: 8px;
display: -webkit-flex;
margin-bottom: 24px;
width: 100%;
background-color: ${props => props.bgColor};
box-sizing: border-box;
`;
interface Props {
text: string;
isError: boolean;
}
export default memo(Alert);
const FormControlSX = useMemo(
() => ({
width: '100%',
'& .MuiFilledInput-root': {
borderTopLeftRadius: '8px',
borderTopRightRadius: '8px',
backgroundColor: palette.white,
},
}),
[],
);
Let me know there is an incorrect use.
All three are related to optimization. If you don't have a performance issues then don't use them. If you have — measure first.
The Alert component is a single and not heavy (I guess) so using memo doesn't improve a performance
useCallback is rarely used. For example, if you use onRequestOTP as prop for a lot of children (or one heavy child) and want to prevent rerendering you can use useCallback in conjunction with memo for a child component
The function inside useMemo is cheap so nothing to do here
Related
I've created a styled MuiDrawer component so I can add some custom styling the component. I want to use the temporary variant but the Drawer is not opening. When I set the Drawer variant to permanent the Drawer does show. So it's probably the passing of the open prop that is causing the error. When I use the default Drawer component from MUI the temporary variant does work:
// demo.tsx
import * as React from 'react';
// import Drawer from '#mui/material/Drawer';
import {Drawer} from './styles';
import Button from '#mui/material/Button';
export default function TemporaryDrawer() {
const [open, setOpen] = React.useState(false);
const toggleDrawer = () => {
setOpen(!open);
};
return (
<>
<Button onClick={toggleDrawer}>Toggle Drawer</Button>
<Drawer
variant='temporary'
open={open}
onClose={toggleDrawer}
>
<p>Drawer</p>
</Drawer>
</>
);
}
// styles.tsx
import {styled} from '#mui/material';
import {Theme, CSSObject} from '#mui/material/styles';
import MuiDrawer from '#mui/material/Drawer';
const drawerWidth = 240;
const openedMixin = (theme: Theme): CSSObject => ({
backgroundColor: 'green',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
});
const closedMixin = (theme: Theme): CSSObject => ({
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)`,
},
});
export 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),
}),
}),
)
https://codesandbox.io/s/temporarydrawer-material-demo-forked-zci40?file=/demo.tsx
While #v1s10n_4 answer is correct, I'll explain a bit more the reason.
The purpose of the shouldForwardProp callback is to prevent the styling props created by the HOC from leaking to the DOM element leading to invalid attribute error. Your Drawer has an open prop, it's a known prop of Dialog so you don't need to be concerned about the prop is not handled properly here:
const Dialog = (props) => {
// I know this open prop, so I'm gonna extract it here
const { open, ...other } = props
// and do some logic with the open prop
// and make sure it is not passed to the Component
return <Component {...other} />
}
However, if you pass an arbitrary prop that is not from the Dialog API, like bgcolor for example:
<Dialog bgcolor='red'
Then it will be passed down to the DOM element:
const Dialog = (props) => {
const { open, ...other /* other includes the bgcolor prop */ } = props
// logic...
return <Component {...other} />
}
When you are using styled to create a styled component:
const StyledDialog = styled(Dialog)(...)
<StyledDialog bgcolor='red'
It'd look like this behind the scene:
const StyledDialog = (props) => {
const className = getStyles(props);
return <Dialog {...props} className={className} />
}
That's why you need to use shouldForwardProp, to filter out the style-related props (not the Dialog props) so it won't be passed down to the Dialog:
const StyledDialog = (props) => {
const { bgcolor, ...other } = props;
const className = getStyles(props);
// bgcolor is filtered now.
return <Dialog {...other} className={className} />
}
you can remove {shouldForwardProp: (prop) => prop !== 'open'} in your styled Drawer definition.
codesandbox
I have a simple React app that gets CSS from an external source in JSON form, the styles look like this:
{
"DOMElements": [
{
"padding": "1rem",
"margin": "5rem",
},
{
"boxSizing": "border-box",
"height": "10px",
}
]
}
So when I get a response like above I want end up with something like this:
import styled from 'styled-components';
const DOMElement1 = styled.div`
padding: 1rem,
margin: 5rem,
`;
const DOMElement2 = styled.div`
boxSizing: border-box,
height: 10px,
`;
const Page = ({ children }) => (
<>
<DOMElement1>{children[1]}</DOMElement1>
<DOMElement2>{childrem[2]}</DOMElement2>
</>
);
It's worth noting that number of DOMElements in unknown, it might be 1, it might be 50.
The component part is easy, I can do just do indexed map and increase index every loop.
The problem I face is - how do I create dynamic styled-components props based on JSON response? I need to do it within the component itself as that's where I know how DOMElements look like, but styled-components are supposed to be outside of the component function... What am I missing? Is it even doable?
You can give props for styled components.
const DOMElement1 = styled.div`
padding: ${({padding}) => padding}rem,
margin: ${({margin}) => margin}rem,
`;
const Page = ({ children }) => (
<>
<DOMElement1 padding={valueFromJson} margin={valueFromJson}>{children[1]}</DOMElement1>
<DOMElement2 padding={valueFromJson} margin={valueFromJson}>{childrem[2]}</DOMElement2>
</>
);
Something like this:
const items = [
{
background: "blue",
width: "30px",
height: "30px",
padding:'10px'
},
{
height: "40px",
background: "red",
width: "30px",
padding:'10px'
}
];
const components = items.map((item) =>
styled("div")({
...item
})
);
export default function App() {
return (
<div style={{ background: "lightblue", width: "100vw", height: "100vh" }}>
{components.map(Comp => <Comp>hi</Comp>)}
</div>
);
}
A simple & straightforward solution would be taking props dynamically in your styled component and this way you don't need to worry about any incoming css property.
Refer the following example/ this snippet -
const DOMElement = styled.div`
${props => props}
`;
const Page = () => {
const data = {
"DOMElements": [
{
"padding": "1rem",
"margin": "5rem",
},
{
"boxSizing": "border-box",
"height": "10px",
}
]
};
return (
<>
{data?.DOMElements?.map((obj, index) =>
<DOMElement {...obj} key={index}>abcd</DOMElement>
)}
</>
);
}
Output snap -
I have this component
import React, { FC } from "react";
import { Avatar } from "#material-ui/core";
export interface AvatarProps {
alt?: string;
src: string;
variant?: "circle" | "rounded" | "square";
sizes?: string;
}
const Component: FC<AvatarProps> = (props: AvatarProps): JSX.Element => {
return <Avatar {...props}></Avatar>;
};
export default Component;
I am trying to set the sizes property but it is not changing. What exactly does it take value?
MUI 5
Using the sx prop provided by the library:
<Avatar sx={{ height: '70px', width: '70px' }}></Avatar>
...or my preferred method, create a styled component outside your functional component or class. Something like this:
const StyledAvatar = ({ children, ...props }) => (
<Avatar sx={{ height: '70px', width: '70px' }} {...props}>
{children}
</Avatar>
);
Usage:
<StyledAvatar alt="add other props as normal">
Children can be nested here
</StyledAvatar>;
Material UI 4 (Original Answer)
Scroll down to the sizes attribute of img and have a read.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
Or just use makeStyles as seen in the documentation:
https://v4.mui.com/components/avatars/#SizeAvatars.js
Or another option is a simple inline style:
<Avatar style={{ height: '70px', width: '70px' }}></Avatar>
You can set the size with a classname and the theme.spacing from Material UI.
const useStyles = makeStyles((theme) => ({
sizeAvatar: {
height: theme.spacing(4),
width: theme.spacing(4),
},
}));
...
const classes = useStyles();
<Avatar src="/path/to/image" alt="Avatar" className={classes.sizeAvatar} />
You can try setting additional Avatar Props
<AvatarGroup
componentsProps={{
additionalAvatar: {
sx: {
height: 30,
width: 30,
background: "red"
}
}
}
}
max={2}>
....
Formatted by https://st.elmah.io
When the user hovers over a Card component, I'd like to show a button on that component that is otherwise invisible. In CSS, I'd do something like this:
.card:hover my-button {
display: block;
}
How do I replicate this in the "Material-UI" way?
All the Material-UI tips I found so far suggest something like this to add hover styling, but this applies the styles to the component that is being hovered over and not a different one.
'&:hover': {
background: 'blue'
}
You can do the exact same thing with CSS using the createMuiTheme:
export const theme = createMuiTheme({
overrides: {
// For label
MuiCard: {
root: {
"& .hidden-button": {
display: "none"
},
"&:hover .hidden-button": {
display: "flex"
}
}
}
}
});
Give the Button inside your Card the className hidden-button and you will get the same thing that you want.
Check it here: https://codesandbox.io/s/mui-theme-css-hover-example-n8ou5
It is not specific to Material UI but a react specific thing. you need a state variable to show/hide your button.
const App = () => {
const [show, setShow] = useState(false);
return (
<Card
onMouseOver={() => setShow(true)}
onMouseOut={() => setShow(false)}>
<CardBody>
// some content
{show && <Button>Click</Button>}
</CardBody>
</Card>
);
}
If you want to define this purely inside of a styled component instead of using either of createMuiTheme or makeStyles, then try the following.
We will give an id to each child component and reference them when defining the behaviour to implement when we hover over the parent component:
const NameCellBox = styled(Box)(({ theme }) => ({
...other styles,
"&:hover #cellBoxLengthTypo": {
display: "none",
},
"&:hover #cellBoxContentTypo": {
display: "inline-block",
},
}));
const CellBoxLengthTypo = styled(Typography)(({ theme }) => ({
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.h5.fontSize,
}));
const CellBoxContentTypo = styled(Typography)(({ theme }) => ({
display: "none",
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.h5.fontSize,
}));
const items: string[] = ["andrew", "barry", "chris", "debbie"];
return (
<>
<NameCellBox>
<CellBoxLengthTypo id="cellBoxLengthTypo">+{items.length}</CellBoxLengthTypo>
<CellBoxContentTypo id="cellBoxContentTypo">{items.join(", ")}</CellBoxContentTypo>
</NameCellBox>
</>
);
Found it useful for updating a DataGrid cell in Material UI when hover or other event fired.
I faced this problem today and after I discussed it with my mentor I made this result and it worked well for me. but first, let me tell you the disadvantage of using eventListener like onMouseEnter & on MouseLeave that it will cause so many renders.
I gave the (parent component) a movie-card class and the (child component) a movie-card-content class like the following
// movie-card.css
.movie-card-content {
opacity: 0;
}
.movie-card:hover .movie-card-content {
opacity: 1;
}
MUI allows you to add className prop so I gave the proper classNames
//MovieCard.jsx (component)
import "./movie-card.css";
function MovieCard () {
return (
<Card className="movie-card">
<CardContent className="movie-card-content">...<CardContent>
</Card>
);
}
and that's it 😉
import {
makeStyles
} from '#material-ui/core'
const useStyles = makeStyles(() => ({
root: {
"& .appear-item": {
display: "none"
},
"&:hover .appear-item": {
display: "block"
}
}
}))
export default function MakeTextAppearOnHover() {
const classes = useStyles()
return (
<div className = { classes.root }>
Hello world
<span className = 'appear-item' >
Appearing text Go
</span>
</div>
)
}
This is a material UI example that displays the sub-element on hover of the parent.
I also noticed that using some of the examples above, when using Material UI's makeStyles, the overlay item was flickering a lot when clicked. This solution below does not flicker when sub-element is clicked.
import React from "react"
import { Card, CardActionArea, CardContent, CardMedia } from "#material-
ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles(theme => ({
card: {
// some styles
},
cardAction: {
position: "relative"
},
media: {
// some styles
},
overlay: {
position: "absolute",
top: "85px"
}
}));
const App = () => {
const classes = useStyles();
const [show, setShow] = React.useState(false);
const handleMouseOver = () => {
setShow(true);
};
const handleMouseOut = () => {
setShow(false);
};
return (
<Card>
<CardActionArea
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut} className={classes.card} >
<CardMedia className={classes.media} component="img" >
// some content
</CardMedia>
<CardContent className={classes.overlay} style={{ display: show ?
'block' : 'none' }>
// some content
</CardContent>
</CardActionArea>
</Card>
);
}
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. ✨🔥