React render vs. UseState - reactjs

I'm pretty new to this and can't really figure out how the render works - stuck with UseState for now :) -. Since I see a lot of people using it I wanted to ask for advice on what I'm doing wrong.
Here are 2 examples one with UseState that works great and other with render that does nothing, I added some style so it looks kind of acceptable for anyone trying it out.
Any help appreciated, thank you
// USESTATE
const [newColor, setNewColor] = useState('green');
useEffect(() => {
setBorder()
}, [newColor])
function setBorder() {
if (newColor === 'green') {
document.getElementById('green').style.borderColor = "orange";
document.getElementById('yellow').style.borderColor = "black";
document.getElementById('red').style.borderColor = "black";
}
if (newColor === 'yellow') {
document.getElementById('yellow').style.borderColor = "orange";
document.getElementById('green').style.borderColor = "black";
document.getElementById('red').style.borderColor = "black";
}
if (newColor === 'red') {
document.getElementById('red').style.borderColor = "orange";
document.getElementById('green').style.borderColor = "black";
document.getElementById('yellow').style.borderColor = "black";
}
}
const changeBg = () => {
document.body.style.backgroundColor = newColor;
}
return (
<div>
<div style={{width: '240px', height: '88px', border: '3px solid black', 'borderRadius': '10px'}}>
<div id="green" onClick={(e) => setNewColor(e.target.id)} style={{width: '40px', height: '40px', background: 'green', border: '3px solid black', float: 'left', margin: '20px 0 0 30px', 'borderRadius': '10px', cursor: 'pointer'}}></div>
<div id="yellow" onClick={(e) => setNewColor(e.target.id)} style={{width: '40px', height: '40px', background: 'yellow', border: '3px solid black', float: 'left', margin: '20px 0 0 20px', 'borderRadius': '10px', cursor: 'pointer'}}></div>
<div id="red" onClick={(e) => setNewColor(e.target.id)} style={{width: '40px', height: '40px', background: 'red', border: '3px solid black', float: 'left', margin: '20px 0 0 20px', 'borderRadius': '10px', cursor: 'pointer'}}></div>
</div>
<button onClick={changeBg}>SAVE</button>
</div>
);
// RENDER
class Test1 extends Component {
constructor(props) {
super(props);
this.state = {
newColor: 'green'
}
}
componentDidUpdate() {
if (this.newColor === 'green') {
document.getElementById('green').style.borderColor = "orange";
document.getElementById('yellow').style.borderColor = "black";
document.getElementById('red').style.borderColor = "black";
}
if (this.newColor === 'yellow') {
document.getElementById('yellow').style.borderColor = "orange";
document.getElementById('green').style.borderColor = "black";
document.getElementById('red').style.borderColor = "black";
}
if (this.newColor === 'red') {
document.getElementById('red').style.borderColor = "orange";
document.getElementById('green').style.borderColor = "black";
document.getElementById('yellow').style.borderColor = "black";
}
}
render() {
const changeBg = () => {
document.body.style.backgroundColor = this.newColor;
}
return (
<div>
<div style={{width: '240px', height: '88px', border: '3px solid black', 'borderRadius': '10px'}}>
<div id="green" onClick={(e) => this.state={newColor: e.target.id}} style={{width: '40px', height: '40px', background: 'green', border: '3px solid black', float: 'left', margin: '20px 0 0 30px', 'borderRadius': '10px', cursor: 'pointer'}}></div>
<div id="yellow" onClick={(e) => this.state={newColor: e.target.id}} style={{width: '40px', height: '40px', background: 'yellow', border: '3px solid black', float: 'left', margin: '20px 0 0 20px', 'borderRadius': '10px', cursor: 'pointer'}}></div>
<div id="red" onClick={(e) => this.state={newColor: e.target.id}} style={{width: '40px', height: '40px', background: 'red', border: '3px solid black', float: 'left', margin: '20px 0 0 20px', 'borderRadius': '10px', cursor: 'pointer'}}></div>
</div>
<button onClick={changeBg}>SAVE</button>
</div>
);
}
}

First of all, you should never* update DOM directly in react as you do.
In both of the examples you are doing that.
The reason why second example doesn't work, componentDidUpdate is called when update happens (e.g. state change or props change), that doesn't happen because you aren't calling setState in second example; rather directly assigning to this.state. Also it should be this.state.newColor as noted in comments.
Idiomatic way to solve this in react is this:
export default function App() {
const [newColor, setNewColor] = React.useState('green');
const changeBg = () => {
document.body.style.backgroundColor = newColor;
};
return (
<div>
<div
style={{
width: '240px',
height: '88px',
border: '3px solid black',
borderRadius: '10px',
}}
>
{['green', 'yellow', 'red'].map((x) => (
<div
onClick={(e) => setNewColor(x)}
style={{
width: '40px',
height: '40px',
background: x,
border: '3px solid black',
borderColor: newColor === x ? 'orange' : 'black',
float: 'left',
margin: '20px 0 0 30px',
borderRadius: '10px',
cursor: 'pointer',
}}
></div>
))}
</div>
<button onClick={changeBg}>SAVE</button>
</div>
);
}
Although the boxes aren't well centered now, because you had different margin for first element, when all divs were written out by hand. But now I have same margin for all of them because I used a loop. I will leave it up to you to adjust it.
* Well in rare cases you can, when say integrating with 3rd party libraries, but in that case you should make sure react doesn't touch that part of the DOM.

Related

Changing Button Background Color Conditionally in Material UI

i got a button that the its background color suppose to change base on state condition , green color for correct , and red for incorrect , so i just made three different css classes , all have similar attributes except the background color , classes changes base on a state with the optionBt class as a default value , how to add an interpolation to optionBt backgroundColor so i only have one class for this button?
const useStyles = makeStyles({
optionBt: {
width: "80%",
color: "#fff",
height: "2.5rem",
marginTop: "0.5rem",
borderRadius: "05%",
border: "2px solid #555",
cursor: "pointer",
},
optioncorrect: {
width: "80%",
color: "#fff",
height: "2.5rem",
marginTop: "0.5rem",
borderRadius: "05%",
border: "2px solid #555",
cursor: "pointer",
backgroundColor: "green",
},
optionuncorrect: {
width: "80%",
color: "#fff",
height: "2.5rem",
marginTop: "0.5rem",
borderRadius: "05%",
border: "2px solid #555",
cursor: "pointer",
backgroundColor: "red",
},
},
},
const QuestionOptions = ({ country, correctness }) => {
const classes = useStyles();
const [myColor, setMyColor] = useState(classes.optionBt);
return (
<Box className={myColor}>
<Button
variant="contained"
onClick={() => {
changeColor();
}}
>
{country}
</Button>
</Box>
);
};
const style={
button: {
width: "80%",
color: "#fff",
height: "2.5rem",
marginTop: "0.5rem",
borderRadius: "05%",
border: "2px solid #555",
cursor: "pointer",
'&:[is-correct="true"]'{
backgroundColor: "green"
},
'&:[is-correct="false"]'{
backgroundColor: "red"
}
}
}
const QuestionOptions = ({ country, correctness }) => {
// set state for true false condition
[isCorrect, setIsCorrect] = useState(undifind)
return (
<Box
<Button
is-correct={isCorrect === true ? 'true': isCorrect === false ? 'false':undifind}
className={style.button}
variant="contained"
onClick={() => {
// if correct set to true or false is uncorrect
setIsCorrect(true);
}}
>
{country}
</Button>
</Box>
);
};
If you want only one class you can do :
const useStyles = makeStyles({
optionBt: {
width: "80%",
color: "#fff",
height: "2.5rem",
marginTop: "0.5rem",
borderRadius: "05%",
border: "2px solid #555",
cursor: "pointer",
},
optioncorrect: {
width: "80%",
color: "#fff",
height: "2.5rem",
marginTop: "0.5rem",
borderRadius: "05%",
border: "2px solid #555",
cursor: "pointer",
backgroundColor: "green",
},
optionuncorrect: {
width: "80%",
color: "#fff",
height: "2.5rem",
marginTop: "0.5rem",
borderRadius: "05%",
border: "2px solid #555",
cursor: "pointer",
backgroundColor: "red",
},
},
},
const QuestionOptions = ({ country, correctness }) => {
const classes = useStyles();
const [myColor, setMyColor] = useState(classes.optionBt);
// set state for true false condition
[correctOrNot, setCorrectOrNot] = useState(null)
return (
<Box className={myColor}>
<Button
variant="contained"
onClick={() => {
// if correct set to true or false is uncorrect
setCorrectOrNot(true);
setsetMyColor(correctOrNot ? classes.optioncorrect : classes.optionuncorrect);
}}
>
{country}
</Button>
</Box>
);
};

How to add a label to a border in mui?

I would like to have a list wrapped in a border which looks and behaves the same as a textfield border:
Example textfield and list which should have both same border.
In the image, the border around the list looks similar than the one around the textfield but most notably, the label is missing. How can I add the label and how would I set up the focus listeners to get the same hover and selection behaviour?
The typescript code for the list:
<List dense sx={{ borderRadius: 1, border: 1, borderColor: 'grey.600'}}>
<ListItem secondaryAction={<IconButton edge="end" aria-label="delete"><DeleteIcon /></IconButton>}>
<ListItemText primary="primary" secondary="group id"/>
</ListItem>
</List>
I am also open for alternative approaches. Thanks for the help.
Here is my answer using React and Mui (only for icon).
It relies on flex.
We have a main container that only draws its left, bottom and right borders.
Then we have a header container in charge of drawing the top border in two parts (before and after) and a section with an icon and title.
You can either pass an icon and a title, just a title, just an icon, or nothing at all.
borderedSection.js:
import React from "react";
import SvgIcon from "#mui/material/SvgIcon";
import styles from "./borderedSection.module.scss";
function BorderedSection({ icon, title, children }) {
return (
<div className={styles.mainContainer}>
<div className={styles.header}>
<div className={styles.headerBorderBefore}></div>
{(icon || title) && (
<div className={styles.headerTitle}>
{icon && <SvgIcon component={icon} />}
{title && <span className={styles.title}>{title}</span>}
</div>
)}
<div className={styles.headerBorderAfter}></div>
</div>
<div className={styles.childrenContainer}>{children}</div>
</div>
);
}
export default BorderedSection;
borderedSection.module.scss:
$border-color: #b2b2b2;
.mainContainer {
display: flex;
flex-direction: column;
max-width: 100%;
border-left: 1px solid $border-color;
border-bottom: 1px solid $border-color;
border-right: 1px solid $border-color;
border-radius: 5px;
margin: 1em;
.childrenContainer {
padding: 1em;
}
.header {
display: flex;
flex-direction: row;
width: 100% !important;
.headerBorderBefore {
border-top: 1px solid $border-color;
width: 1em;
border-top-left-radius: 5px;
}
.headerTitle {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
gap: 0.25em;
width: fit-content;
height: 2em;
margin: -1em 0.5em 0em 0.5em;
overflow: hidden;
text-overflow: ellipsis;
font-size: 1em;
font-weight: 600;
}
.headerBorderAfter {
border-top: 1px solid $border-color;
width: 1em;
flex-grow: 2;
border-top-right-radius: 5px;
}
}
}
usage:
import React from "react";
import BorderedSection from "./borderedSection";
import InfoIcon from "#mui/icons-material/Info";
function Example() {
return (
<div style={{ padding: "2em" }}>
<BorderedSection icon={InfoIcon} title="Icon and title">
<div>a first child with quite a long text</div>
<div>a second child</div>
</BorderedSection>
<BorderedSection title="Title only">
<div>a first child with quite a long text</div>
<div>a second child</div>
</BorderedSection>
<BorderedSection icon={InfoIcon} >
<div>Icon only</div>
<div>a second child with quite a long text</div>
</BorderedSection>
<BorderedSection >
<div>No icon and no title</div>
<div>a second child with quite a long text</div>
</BorderedSection>
</div>
);
}
Here is how it looks:
I hope it helps
I now managed to hack a solution which looks the same. I do still hope though that there is a clean way to do this: result.
<FormLabel style={{marginLeft: "0.71em", marginTop: "-0.71em", paddingLeft: "0.44em", zIndex: 2, width: "4.2em", backgroundColor: "#383838", position: "absolute", fontSize: "0.75em"}}>Damage</FormLabel>
<List dense sx={{ borderRadius: 1, border: 1, borderColor: 'grey.600', "&:hover": { borderColor: 'grey.200' }}}>
<ListItem secondaryAction={<IconButton edge="end" aria-label="delete"><DeleteIcon /></IconButton>}>
<ListItemText primary="primary" secondary="group id"/>
</ListItem>
</List>
I needed the same thing. As I was poking around I noticed that MUI accomplished this by using the fieldset tag. I created a quick and dirty component (OutlinedBox) to get this effect:
import React from "react";
import {Box, FormLabel} from "#mui/material";
const OutlinedBox = (props) => {
const {
label,
children
} = props;
return (
<Box>
<FormLabel
sx={{
marginLeft: "0.71em",
marginTop: "-0.71em",
paddingLeft: "0.44em",
paddingRight: '0.44em',
zIndex: 2,
backgroundColor: (theme) => theme.palette.background.default,
position: "absolute",
fontSize: "0.75em",
width: 'auto',
}}>{label}</FormLabel>
<Box
sx={{
position: 'relative',
borderRadius: theme => theme.shape.borderRadius + 'px',
fontSize: '0.875rem',
}}
>
<Box
sx={{
padding: (theme) => theme.spacing(1),
display: 'flex',
gap: (theme) => theme.spacing(1),
flexWrap: 'wrap',
overflow: 'auto'
}}
>
{children}
</Box>
<fieldset aria-hidden={"true"} style={{
textAlign: 'left',
position: 'absolute',
bottom: 0,
right: 0,
top: '-5px',
left: 0,
margin: 0,
padding: '0 8px',
pointerEvents: 'none',
borderRadius: 'inherit',
borderStyle: 'solid',
borderWidth: '1px',
overflow: 'hidden',
minWidth: '0%',
borderColor: 'rgba(255, 255, 255, 0.23)',
}}
>
<legend style={{
float: 'unset',
overflow: 'hidden',
display: 'block',
width: 'auto',
padding: 0,
height: '11px',
fontSize: '0.75em',
visibility: 'hidden',
maxWidth: '100%',
'-webkit-transition': 'max-width 100ms cubic-bezier(0.0, 0, 0.2, 1) 50ms',
transition: 'max-width 100ms cubic-bezier(0.0, 0, 0.2, 1) 50ms',
whiteSpace: 'nowrap',
}}><span>{label}</span></legend>
</fieldset>
</Box>
</Box>
);
}
export { OutlinedBox };
// Example usage: <OutlinedBox label="Test">Some content here</OutlinedBox>
I figured I'd post it here in case anyone needs the same thing and comes across this question. All the styling stuff was copied from the styles MUI was using. There may be a better way to read some of this off of the theme, so if anyone decides to use this you may want to tweak it some.

Button doesn't disappear immediately; need to move mouse during animation using clip-path

I am setting the clipPath property from circle(0%) to circle(100%) using GSAP timeline.
let t1 = useRef();
useEffect(() => {
t1.current = gsap.timeline({
defaults: { duration: 0.5, ease: "Back.easeOut.config(2)" },
});
t1.current.paused(true); //to ensure animation doesn't play immediately
t1.current.to(".overlay", { clipPath: "circle(100%)" });
});
const handleClick = () => {
t1.current.play(); //start the animation
};
const handleClose = () => {
t1.current.reverse(0.2); //reverse the animation from 0.2 seconds
};
Complete React Component code:
import React, { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faWindowClose } from "#fortawesome/free-solid-svg-icons";
export default function GSAPFullScreen() {
let t1 = useRef();
useEffect(() => {
t1.current = gsap.timeline({
defaults: { duration: 0.5, ease: "Back.easeOut.config(2)" },
});
t1.current.paused(true); //to ensure animation doesn't play immediately
t1.current.to(".overlay", { clipPath: "circle(100%)" });
}, []);
const handleClick = () => {
t1.current.play(); //start the animation
};
const handleClose = () => {
t1.current.reverse(0.2); //reverse the animation from 0.2 seconds
};
return (
<>
<div
className="overlay"
style={{
clipPath: "circle(0%)",
width: "100%",
height: "100%",
position: "fixed",
overflowY: "scroll",
overflowX: "hidden",
backgroundColor: "purple",
}}
>
<FontAwesomeIcon
icon={faWindowClose}
size="2x"
style={{
position: "absolute",
top: "2rem",
right: "2rem",
color: "white",
cursor: "pointer",
}}
onClick={handleClose}
/>
<div className="container md" style={{ color: "white" }}>
<br />
<div style={{ fontWeight: "bold" }}>This is an amazing Question</div>
<div>What is your question? Can you guess?</div>
<div>Option 1</div>
<div>Option 2</div>
<div>Option 3</div>
<div>Option 4</div>
</div>
</div>
<div className="container" style={{ height: "100vh" }}>
<div className="flex">
<button className="lg p-1 btn" onClick={() => handleClick()}>
Launch Animation
</button>
</div>
</div>
</>
);
}
Relevant CSS:
.container {
max-width: 1100px; /* Ensures heading is in center beyond 1100px*/
margin: 0 auto; /* Ensures to keep the 1100px container in middle of the screen;
until 1100px it will be on the side and this property will not have any affect*/
overflow: auto; /* This removes the space on the top of the heading which was created because of margin: 10px 0 on h1*/
padding: 0 40px;
}
.btn {
display: inline-block;
padding: 10px 30px;
cursor: pointer;
background: var(--primary-color);
color: #fff;
border: none;
border-radius: 5px;
}
.md {
font-size: 2rem;
}
.lg {
font-size: 3rem;
}
.flex {
display: flex;
justify-content: center; /* aligns along the main axis*/
align-items: center;
height: 100%;
}
.p-1 {
padding: 1rem; /*1 rem is usually 16px depending the size at root*/
}
.btn:hover:enabled{
transform: scale(0.98); /*reduces the size of button a bit*/
}
When the button has the pseudo class :hover a transform will be applied to the element, which means that it the stacking context is changed (see also Stacking without the z-index property).
To fix this you can add z-index: 1 to the overlay class or remove the transform from the :hover class (Not ideal).

Why intersection observer triggers on component mount?

Following is the sample example I was trying to understand intersection observer:
function Test(props) {
const loadingRef = useRef(null);
useEffect(() => {
let options = {
root: null,
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(handleIntersection, options);
observer.observe(loadingRef.current)
}, [])
function handleIntersection(x, y) {
console.log("Why this triggers on component mount?");
}
return (
<div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div style={{width: '100%', height: '100px', background: '#c4c4c4', borderBottom: '1px solid #f4f4f4'}}></div>
<div ref={loadingRef}></div>
</div>
);
}
I can't understand as to why this triggers on component mount even when target element doesn't intersect with the source element.
Your useEffect has a second argument:
useEffect(() => {
let options = {
root: null,
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(handleIntersection, options);
observer.observe(loadingRef.current)
}, [])
^^
||
These here.
This means that it is run when the component mounts. There is a part of the Hooks FAQ on reactjs.org that mentions this.

use conditional inside style inside render

I have this render and I have notification.isError in props, but I don't know how to use conditional to print a value inside background value, to get different colors accord to the property is Error.
I get "Unexpected token"
render() {
return (
<div style={{ padding: '4px 10px 1px 10px',
fontSize: '16px',
backgroundColor: {return (this.props.notification.isError?'red':'black')},
}}
>
I've tried this too:
const bgcolor = (this.props.notification.isError?'red':'black');
return (
<div style={{ padding: '4px 10px 1px 10px',
backgroundColor: {bgcolor},
}}
but backgroundColor is not showed on the browser; it's like react don't like the way to send the value
Can you try this:
render() {
return (
<div style={{ padding: '4px 10px 1px 10px',
fontSize: '16px',
backgroundColor: (this.props.notification.isError?'red':'black')
}}
>

Resources