How to pass props fill value to makeStyles' keyframe? Do I have to specify initial states to pass the prop?
It works for the color but does not work for fillvalue.
---Child component
const useStyles = makeStyles({
progress: {
animation: '$load 3s normal forwards',
background: props => props.color,
width: '0',
},
"#keyframes load": {
"0%": { width: "0" },
"100%": { width: props => props.fillvalue}
}
});
export default function ProgressBar(props) {
const propsStyle = {color: props.color, fillvalue: props.fillvalue}
const classes = useStyles(propsStyle)
return(
<div>
<div className={classes.progress}>
</div>
</div>
);
}
---Parent
function App() {
return (
<div>
<ProgressBar color="#000" fillvalue = "60%"/>
</div>
);
}
The answer to this is - you can't (at least for now). This is a bug as of this writing (MUI latest release is v4.11.0 as of this writing) and is acknowledged by one of the MUI contributors. You can track its progress at this issue: https://github.com/mui-org/material-ui/issues/21011
You are going to have to find other means of passing those props without the use of keyframes
const useStyles = makeStyles({
progress: {
height: "10px",
background: (props) => props.color,
width: (props) => props.fillvalue,
transition: "width 3s"
}
});
function ProgressBar(props) {
const propsStyle = { color: props.color, fillvalue: props.fillvalue };
const classes = useStyles(propsStyle);
return (
<div>
<div className={classes.progress}></div>
</div>
);
}
function App() {
const [fill, setFill] = React.useState("0%");
return (
<div>
<button onClick={() => setFill("0%")}>0%</button>
<button onClick={() => setFill("60%")}>60%</button>
<ProgressBar color="#aaa" fillvalue={fill} />
</div>
);
}
ReactDOM.render(<App/>,document.getElementById("root"));
<body>
<div id="root"></div>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script>
<script type="text/babel">
const { makeStyles } = MaterialUI;
</script>
</body>
I see that there is no shorter way to do this, so I've discovered a really easy way to pass the props to the #keyframes inside makeStyles You just have
to create a "helper" hook in order to pass the props:
export const useAnimationStyles = ({ width, duration }: Props) => {
const classes = makeStyles({
'#keyframes animation': {
'0%': {
transform: 'translateX(0px)',
},
'100%': {
transform: `translateX(${width}px)`,
},
},
scroll: {
animation: `$animation linear infinite`,
animationDuration: `${duration}s`
},
})
return classes()
}
and you can use it as usual:
const classes = useAnimationStyles({width, duration})
...
<div className={classes.scroll}
Related
I have a problem to write code:
I have a state
const [theme, setTheme] = useState({ mode: "LIGHT" });
and I want to made a toggle function that change mode to 'DARK' and change DARK to 'LIGHT' by double click. how can I write it?
import { createContext, useContext, useState } from "react";
const DARK = "DARK";
const ThemeContext = createContext();
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState({ mode: "LIGHT" });
const toggleThemeMode = () => {
setTheme();
console.log(theme);
};
return (
<ThemeContext.Provider value={{ theme, toggleThemeMode }}>
{children}
</ThemeContext.Provider>
);
};
const useTheme = () => useContext(ThemeContext);
export { ThemeProvider, useTheme, DARK };
A sample e.g. with onDoubleClick event , but if you mean just on single click change event name to onClick
const {useState} = React;
const App = () => {
const [theme, setTheme] = useState("Dark");
const handleClick = () =>{
setTheme(prev=> prev === "Dark" ? "Light" : "Dark")
}
return (
<div className={`theme ${theme}`}>
<h2> Double click to change the theme - current theme: {theme} </h2>
<br />
<br />
<button className={`btn ${theme}`} onDoubleClick={handleClick}>Change Theme</button>
<br />
<br />
<p>Some test data </p>
</div>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.Dark{
background: black;
color: white;
}
.Light{
background: white;
color: black;
}
.theme{
height: 100vh;
}
.btn{
border: 2px solid pink;
background: gray;
border-radius: 5px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
You can just use the following code:
const [theme, setTheme] = useState("LIGHT");
...
setTheme(theme === DARK ? "LIGHT" : DARK);
Nevertheless I suggest you to at least provide also a const for "LIGHT" value or even better: an enum. If you want to stick with two values (light and dark), you can also define the state as a boolean.
Expected Scenario: if the user doesn't use the sidebar for more than 5 seconds, the sidebar fadeout (opacity set to 20%) and fade in (opacity:100%) when mouse is 20px nearby.
Current implementation what i got is: when I load the page and on sidebar if its not hovered for 5 sec, its faded out and when i hover on it, it fades in (opacity 100%) and when i hover out (mouseleave) it is 100%.
I am failing to achieve when mouse is 20px nearby - fade in.
const [isSidebarFaded, setSidebarFaded] = useState(false)
const [isHovered, setHovered] = useState(false)
useEffect(() => {
if (!isHovered) {
const timerId = setTimeout(() => {
//after 5 seconds if not hovered
setSidebarFaded(true)
}, 5000)
return () => {
clearTimeout(timerId)
}
}
}, [])
const handleMouseEnter = () => {
setHovered(true)
if (isSidebarFaded) {
setSidebarFaded(false)
}
}
const handleMouseLeave = () => {
setHovered(false)
if (isSidebarFaded)
setSidebarFaded(true)
}
return(
<div
className={classNames(styles.component, {
[styles.fadeOut]: isSidebarFaded,
})}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
)
Create a wrapper around the actual sidebar, with a padding of 20px. Put the event handlers on the wrapper.
In my version of your code, there is a single timeout with ref, that the event handlers start/stop:
const { useState, useRef, useCallback, useEffect } = React
const Demo = () => {
const [isSidebarFaded, setSidebarFaded] = useState(false)
const timeout = useRef()
const handleMouseEnter = useCallback(() => {
setSidebarFaded(false)
clearTimeout(timeout.current)
}, [])
const handleMouseLeave = useCallback(() => {
clearTimeout(timeout.current)
timeout.current = setTimeout(() => {
setSidebarFaded(true)
}, 2000)
}, [])
useEffect(() => {
handleMouseLeave();
return () => {
clearTimeout(timeout.current)
}
}, [handleMouseLeave])
return(
<div
className={classNames({ sidebarContainer: true, sidebarFaded: isSidebarFaded })}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className="sidebar"></div>
</div>
)
}
ReactDOM
.createRoot(root)
.render(<Demo />)
.sidebarContainer {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100px;
padding-right: 20px;
opacity: 1;
transition: opacity 0.3s;
}
.sidebarFaded {
opacity: 0.2;
}
.sidebar {
height: 100%;
width: 100%;
background: blue;
}
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.3.2/index.min.js" integrity="sha512-GqhSAi+WYQlHmNWiE4TQsVa7HVKctQMdgUMA+1RogjxOPdv9Kj59/no5BEvJgpvuMTYw2JRQu/szumfVXdowag==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="root"></div>
These are the problems I faced with Next.js and Material UI, even though I am following the methods mentioned in Material UI docs.
I am getting this error, if I use props with makeStyles:
I am getting if I pass state as props
const useStyles = makeStyles((theme: Theme) => ({
root: {
background: "#EFEFEF",
minHeight: (props: any) => (props.open ? "150vh" : "100vh"),
},
},
}));
const Profile = () => {
const [open, setOpen] = useState(false);
const classes = useStyles({ open });
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<Layout authRoute={false} showNavbar={false}>
<Container maxWidth="lg">
<Header method={method} />
<div className={classes.btnContainer}>
<Button
variant="contained"
color="secondary"
className="yesBtn"
onClick={handleOpen}>
<IoMdCheckmark />
Yes
</Button>
<Button
variant="contained"
color="default"
className="noBtn"
onClick={() => router.push("/")}>
<TiDeleteOutline />
No
</Button>
</div>
<ProfileModal handleClose={handleClose} open={open} />
</Container>
</Layout>
</div>
);
};
export default Profile;
Media query is not working in production (theme.breakpoints.down("sm")).
export const useButtonStyle = makeStyles(theme => ({
submitBtn: {
padding: "12px 16px",
borderRadius: theme.shape.borderRadius,
position: "absolute",
bottom: "25%",
display: "block",
left: "0%",
right: "0%",
width: "83.5%",
margin: " auto",
[theme.breakpoints.down("xs")]: {
bottom: "35%",
width: "88%",
},
"& .MuiButton-label": {
display: "flex",
justifyContent: "center",
alignItems: "center",
"& .MuiCircularProgress-root": {
height: "20px",
width: "20px",
marginRight: "10px",
},
},
},
root: {
position: "relative",
backgroundColor: theme.palette.secondary.dark,
minHeight: "100vh",
[theme.breakpoints.up("sm")]: {
padding: "2rem 0.2rem",
"& .overlay": {
display: "none",
},
},
[theme.breakpoints.down("sm")]: {
"& .MuiContainer-root": {
paddingLeft: "0",
paddingRight: "0",
},
},
},
}));
Here is _document.tsx file
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "#material-ui/styles";
class MyDocument extends Document {
static async getInitialProps(ctx: any) {
const sheet = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App: any) => (props: any) =>
sheet.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
ctx.renderPage(sheet);
}
}
render() {
return (
<Html>
<Head>
<link
rel="shortcut icon"
type="image/png"
href="../static/favicon.ico"
/>
<style>{`body { margin: 0 } /* custom! */`}</style>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
</Head>
<body className="custom_class">
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
This is _app.tsx file
import { ApolloProvider } from "#apollo/client";
import CssBaseline from "#material-ui/core/CssBaseline";
import { ThemeProvider } from "#material-ui/core/styles";
import Head from "next/head";
import React from "react";
import theme from "../src/theme";
import "../styles/styles.scss";
import { withApollo } from "../src/utils/apollo";
import App from "next/app";
class MyApp extends App<any> {
componentDidMount() {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement!.removeChild(jssStyles);
}
}
render() {
const { Component, pageProps, apolloClient } = this.props;
return (
<>
<Head>
<title>Beauty wall spot</title>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
</ThemeProvider>
</>
);
}
}
export default withApollo(MyApp);
Install 'npm install #mui/styles'
Then Import: import { makeStyles } from '#mui/styles';
I am building a basic chat application. I have a parent component (Console.js) that contains three child components
QuestionCard.js
AnswerCard.js
InputCard.js
This is how the basic layout looks like
const Console = () => {
// initial state for input field
const [userInput, setUserInput] = useState("");
// fetch previous conversation history
const conversationHistory = useSelector(state=> state.conversationHistory.data)
conversationHistory.map((info, idx) => (
info.type == "statement" ?
<span key={idx}>
<QuestionCard
data={info}
/>
</span>
: info.type == "answer" ?
<span key={idx}>
<AnswerCard
userInput={userInput}
setUserInput={setUserInput}
data={info}
/>
</span>
:
<span></span>
))
<InputCard
userInput={userInput}
setUserInput={setUserInput}
/>
}
Specifically in the InputCard.js child component, there resides the input field where the user types
const InputCard = ({userInput, setUserInput}) => {
const handleTextBoxInput = e => {
setUserInput(e.target.value)
}
return (
<input
type="text"
value={userInput || ""}
onChange={handleInput}
id="userQuery"
/>
)
}
The problem here is that every time I press a key, all the child components (QuestionCard.js, AnswerCard.js, InputCard.js) re-renders.
I read about memo and it is one way to ensure components don't re-render but needs something to compare against. So I understand I need to compare the userInput state before and after and check if indeed something changed. But I just don't know where do I do this comparison or whether to even use memo
Can anybody help me with this?
Note: I understand I can put the setState inside the InputCard component and re-rendering will stop but as you can see, I need the setState variables inside the AnswerCard too for some processing.
That's how React works AFAIK. A change in props, triggers a render of the component.
That said,
Here are some suggestions for your problem:
Debounce
One of the common patterns around handling user input is to debounce it. This prevents the component re-render on every key press.
You can tailor the debounce timer to suit your use-case:
const Border = {
RED: {
border: '1px solid red',
margin: '12px 0',
padding: '4px'
},
GREEN: {
border: '1px solid green',
margin: '12px 0',
padding: '4px'
},
BLUE: {
border: '1px solid blue',
margin: '12px 0',
padding: '4px'
},
MAGENTA: {
border: '1px solid magenta',
margin: '12px 0',
padding: '4px'
}
};
const MARGIN = { margin: '12px' };
function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = React.useState(value);
React.useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}
const useRenderCounter = (thing) => {
const renderCount = React.useRef(1);
React.useEffect(() => {
renderCount.current += 1;
});
return `Render count for ${thing} ${renderCount.current}`;
};
const InputCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('InputCard');
const [input, setInput] = React.useState(userInput);
const debouncedValue = useDebounce(input, 750);
React.useEffect(() => {
setUserInput(debouncedValue);
}, [debouncedValue]);
return (
<div style={Border.MAGENTA}>
<span>{renderCount}</span>
<div style={MARGIN}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
id="userQuery"
/>
</div>
</div>
);
};
const QuestionCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('QuestionCard');
return (
<div style={Border.RED}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
const AnswerCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('AnswerCard');
return (
<div style={Border.GREEN}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
function App() {
const renderCount = useRenderCounter('App');
const [userInput, setUserInput] = React.useState('');
return (
<div style={Border.BLUE}>
<span>{renderCount}</span>
<QuestionCard userInput={userInput} setUserInput={setUserInput} />
<AnswerCard userInput={userInput} setUserInput={setUserInput} />
<InputCard userInput={userInput} setUserInput={setUserInput} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("react"));
span {
padding: 4px;
font-style: italic;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
useRef
However, if you need the other components to not re-render on entering text, you can leverage useRef hook and keep track of the user input without having to re-render the component tree.
But bear in mind, with this approach, in order for other components to show the updated value on the DOM,
there has to be a trigger to cause a re-render. In the example, the state is updated on button click
which then triggers the re-render. Since you mentioned you are building a chat app, maybe you'd find this pattern useful.
const Border = {
RED: {
border: '1px solid red',
margin: '12px 0',
padding: '4px'
},
GREEN: {
border: '1px solid green',
margin: '12px 0',
padding: '4px'
},
BLUE: {
border: '1px solid blue',
margin: '12px 0',
padding: '4px'
},
MAGENTA: {
border: '1px solid magenta',
margin: '12px 0',
padding: '4px'
}
};
const MARGIN = { margin: '12px' };
const useRenderCounter = (thing) => {
const renderCount = React.useRef(1);
React.useEffect(() => {
renderCount.current += 1;
});
return `Render count for ${thing} ${renderCount.current}`;
};
const InputCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('InputCard');
const [input, setInput] = React.useState(userInput);
React.useEffect(() => {
setUserInput(input);
}, [input]);
return (
<div style={Border.MAGENTA}>
<span>{renderCount}</span>
<div style={MARGIN}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
id="userQuery"
/>
</div>
</div>
);
};
const QuestionCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('QuestionCard');
return (
<div style={Border.RED}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
const AnswerCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('AnswerCard');
return (
<div style={Border.GREEN}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
function App() {
const renderCount = useRenderCounter('App');
const inputRef = React.useRef('');
const setUserInput = (input) => {
inputRef.current = input;
};
const [submit, onSubmit] = React.useState('');
return (
<div style={Border.BLUE}>
<span>{renderCount}</span>
<QuestionCard userInput={inputRef.current} setUserInput={setUserInput} />
<AnswerCard userInput={inputRef.current} setUserInput={setUserInput} />
<InputCard userInput={inputRef.current} setUserInput={setUserInput} />
<button type="submit" onClick={() => onSubmit(inputRef.current)}>
Trigger Render
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("react"));
span {
padding: 4px;
font-style: italic;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
React.memo()
React.memo() is used to memoize a component, based on the dependencies passed to it. It takes a component as a prop and returns a component that prevents a component from re-rendering if the props (or values within it) have not changed.
It is important to keep in mind that your code must not depend on React.memo() just to avoid re-renders. You should be able to replace React.memo() with direct component calls and it must not affect anything else, except performance.
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs. - Docs
I honestly do not see any benefits by using a memo for your case. Anyways, this is ideally how you can memoize a component:
const QuestionCardMemoized = React.memo(
() => QuestionCard({ userInput, setUserInput }),
[userInput, setUserInput]
);
const AnswerCardMemoized = React.memo(
() => AnswerCard({ userInput, setUserInput }),
[userInput, setUserInput]
);
return (
<div>
<QuestionCardMemoized />
<AnswerCardMemoized />
<InputCard userInput={userInput} setUserInput={setUserInput} />
</div>
);
Do keep in mind that React is VERY fast. Unless you see any performance issues by measuring using profiler tools, I'd say spend your time on something more useful than unnecessary/pre-mature optimization.
I want to write and access function/const in CSS style with stateless React Component.
I did below implementation but can't get success:
const MyComponent = ({ title, data }: Props) => {
function setPercentageColor() {
console.log("standard");
return "#ca2626";
}
const percentage = "50";
return (
<div className="progress-circle">
<CircularProgressbar
percentage={percentage}
text={percentage}
styles={{
root: {},
path: {
stroke: "#0080a6",
strokeWidth: "8",
transition: "stroke-dashoffset 0.5s ease 0s"
},
trail: {
stroke: setPercentageColor //here I want to access
},
text: {
fill: "#28323c",
fontSize: "35px",
fontFamily: "Roboto Regular"
}
}}
/>
</div>
);
};
export default MyComponent;
Please help in to write the correct solution.
Thanks
You can access your function directly by calling it. ie:
stroke: setPercentageColor()
Here is a simple working example:
const App = () => {
const setTextColor = () => "#ca2626";
return (
<div>
<Foo
styles={{ colors: { text: setTextColor(), background: "black" } }}
/>
</div>
);
};
const Foo = (props) => {
const { styles } = props;
return (
<div
style={{
color: styles.colors.text,
backgroundColor: styles.colors.background,
}}
>
Foo
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>