how to change jss dynamically - reactjs

I know many ways to do this without JSS, but this paradigm seems to make it very difficult:
const Backdrop = () => {
const {height, width} = useWindowSize()
const css = makeStyles(() => createStyles({
root : {
backgroundColor: 'black',
backgroundImage: 'url(/img/bg.jpg)',
height, // this value should update when window size changes
width,
position:'fixed',
}
}))()
return <div className={css.root}>
</div>
}
Update:
I copy-pasted myself into a way to make this work, but it's just too much code compared to traditional CSS
const [_windowSize, $_windowSize] = useState({ width: window.innerWidth, height: window.innerHeight })
const handler = () => $_windowSize({ width: window.innerWidth, height: window.innerHeight })
useEffect(() => {
window.addEventListener("resize", handler)
return () => window.removeEventListener("resize", handler)
}, [])
return _windowSize
}
const Backdrop = () => {
useWindowSize()
const css = makeStyles(() => createStyles({
root : {
backgroundColor: 'black',
backgroundImage: 'url(/img/bg.jpg)',
backgroundSize: 'cover',
height: () => window.innerHeight,
width: () => window.innerWidth,
position:'fixed',
}
}))()
return <div className={css.root}></div>
}
Somehow makeStyles knows of every update in the DOM. It's really unintuitive.

Might this suit you're need?
const { height, width } = useWindowDimensions();
const style = {
root : {
backgroundColor: 'black',
backgroundImage: 'url(/img/bg.jpg)',
backgroundSize: 'cover',
height: height,
width: width,
position:'fixed'
}
}
return <div style={style.root}></div>
useWindowDimensions is a custom effect defined like this:
/**
* Returns window dimensions, listening to resize event.
*
* Example:
*
* const Component = () => {
* const { height, width } = useWindowDimensions();
* }
*/
export function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(
getWindowDimensions()
);
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowDimensions;
}
the effect listen to window resize and update the {width, height} state causing a clean rerendering.

Related

How to get height and width of a div container in react when i am resizing it using CSS resize property

height and width of a div container in react when I am resizing it using CSS resize property
I have tried this but i am getting error
import React, { useState, useRef, useEffect } from 'react';
const TextAreaWrapper = () => {
const [dimensions, setDimensions] = useState({ height: 0, width: 0 });
const textareaRef = useRef(null);
useEffect(() => {
const textarea = textareaRef.current;
const handleResize = () => {
console.log('Hello world');
setDimensions({
height: textarea.offsetHeight,
width: textarea.offsetWidth,
});
};
textarea.addEventListener('resize', handleResize);
return () => {
textarea.removeEventListener('resize', handleResize);
};
}, []);
return (
<div>
<textarea ref={textareaRef} />
<p>Height: {dimensions.height}</p>
<p>Width: {dimensions.width}</p>
</div>
);
};
export default TextAreaWrapper;
Try this (check that the ref is not null):
useEffect(() => {
const textarea = textareaRef.current;
if (!textarea) {
return;
}
const handleResize = () => {
setDimensions({
height: textarea.offsetHeight,
width: textarea.offsetWidth,
});
};
textarea.addEventListener('resize', handleResize);
return () => {
textarea.removeEventListener('resize', handleResize);
};
}, [textareaRef.current]);

Why are my dimensions not updating on resize when using React Hooks

My dimensions are not updating whenever the window is resized. In the code below you can see the window.innerHeight is updated, but the dimensions are not. I am probably missing something but I have not figured it out yet.
// Navbar.components.ts:
export const sidebar = () => {
let height;
let width;
if (typeof window !== `undefined`) {
height = window.innerHeight
width = window.innerWidth
}
const [dimensions, setDimensions] = useState({
windowHeight: height,
windowWidth: width,
})
useEffect(() => {
const debouncedHandleResize = debounce(function handleResize() {
setDimensions({
windowHeight: window.innerHeight,
windowWidth: window.innerWidth,
});
// Logging window.innerHeight gives the current height,
// Logging dimensions.windowHeight gives the initial height
console.log(window.innerHeight, " . ", dimensions.windowHeight)
}, 100);
window.addEventListener(`resize`, debouncedHandleResize)
return () => window.removeEventListener('resize', debouncedHandleResize)
}, [])
return {
open: () => ({
clipPath: `circle(${dimensions.windowHeight * 2 + 200}px at 40px 40px)`,
transition: {
type: "spring",
stiffness: 20,
restDelta: 2
}
}),
closed: () => ({
clipPath: `circle(30px at ${300 - 40}px ${dimensions.windowHeight - 45}px)`,
transition: {
delay: 0.2,
type: "spring",
stiffness: 400,
damping: 40
}
})
}
}
And I use the sidebar like this:
// Navbar.tsx
const Navbar: React.FC<NavbarProps> = () => {
...
return {
...
<MobileNavBackground variants={sidebar()} />
...
}
}
Here is an example of the logs that are returned when resizing the window:
Update 1
#sygmus1897
Code changed to this:
// Navbar.tsx:
const Navbar: React.FC<NavbarProps> = () => {
const [windowWidth, windowHeight] = getDimensions();
useEffect(() => {
}, [windowWidth, windowHeight])
return (
...
<MobileNavWrapper
initial={false}
animate={menuIsOpen ? "open" : "closed"}
custom={height}
ref={ref}
menuIsOpen={menuIsOpen}
>
<MobileNavBackground variants={sidebar} custom={windowHeight} />
<MobileNav menuIsOpen={menuIsOpen} toggleMenu={toggleMenu} />
<MenuToggle toggle={() => toggleMenu()} />
</MobileNavWrapper>
)
}
// getDimensions()
export const getDimensions = () => {
const [dimension, setDimension] = useState([window.innerWidth, window.innerHeight]);
useEffect(() => {
window.addEventListener("resize", () => {
setDimension([window.innerWidth, window.innerHeight])
});
return () => {
window.removeEventListener("resize", () => {
setDimension([window.innerWidth, window.innerHeight])
})
}
}, []);
return dimension;
};
// Navbar.components.ts
export const sidebar = {
open: (height) => ({
clipPath: `circle(${height + 200}px at 40px 40px)`,
transition: {
type: "spring",
stiffness: 20,
restDelta: 2
}
}),
closed: (height) => ({
clipPath: `circle(30px at ${300 - 60}px ${height - 65}px)`,
transition: {
delay: 0.2,
type: "spring",
stiffness: 400,
damping: 40
}
})
}
The issue remains where resizing the window does not affect the clipPath position of the circle. To illustrate the issue visually, the hamburger is supposed to be inside the green circle:
You can make a custom hook to listen to window resize.
You can modify solution from this link as per you requirement Custom hook for window resize
By using useState instead of ref, updating it on resize and returning the values to your main component
Here's an example:
export default function useWindowResize() {
const [dimension, setDimension] = useState([0, 0]);
useEffect(() => {
window.addEventListener("resize", () => {
setDimension([window.innerWidth, window.innerHeight])
});
return () => {
window.removeEventListener("resize", () => {
setDimension([window.innerWidth, window.innerHeight])
})
}
}, []);
return dimension;
}
and inside your main component use it like this:
const MainComponent = () => {
const [width, height] = useWindowResize();
useEffect(()=>{
// your operations
}, [width, height])
}
Your component will update every time the dimensions are changed. And you will get the updated width and height
EDIT:
Framer-motion provides a way to dynamically set variant's properties(for detailed guide refer to this Dynamically Update Variant) :-
// Navbar.tsx:
const Navbar: React.FC<NavbarProps> = () => {
return (
...
<MobileNavWrapper
initial={false}
custom={window.innerWidth} // custom={window.innerHeight} if variable depends on Height
animate={menuIsOpen ? "open" : "closed"}
custom={height}
ref={ref}
menuIsOpen={menuIsOpen}
>
<MobileNavBackground variants={sidebar} />
<MobileNav menuIsOpen={menuIsOpen} toggleMenu={toggleMenu} />
<MenuToggle toggle={() => toggleMenu()} />
</MobileNavWrapper>
)
}
// Navbar.components.ts
export const sidebar = {
open: (width) => ({
clipPath: `circle(${width+ 200}px at 40px 40px)`,
transition: {
type: "spring",
stiffness: 20,
restDelta: 2
}
}),
closed: (width) => ({
clipPath: `circle(30px at ${300 - 60}px ${width- 65}px)`,
transition: {
delay: 0.2,
type: "spring",
stiffness: 400,
damping: 40
}
})
}
Thanks to this thread: Framer Motion - stale custom value - changing the custom value doesn't trigger an update I found that the issue I'm having is a bug in framer-motion. To resolve this issue, add a key value to the motion component that's having issues re-rendering. This makes sure React re-renders the component.
In my case all I had to do was this:
<MobileNavBackground variants={sidebar} custom={windowHeight} key={key} />

Create simple custom progression bar in React

I'm trying to create my simple progression bar in React using CSS and setInterval. It's not working properly after 10%. Does anyone know why it is happening? Thanks
import React, {useState, useEffect} from 'react';
const Loading = () => {
const [percentage, setPercentage] = useState(0);
const containerStyles = {
height: 20,
width: '100%',
backgroundColor: "#e0e0de",
borderRadius: 50,
margin: 50
}
const fillerStyles = {
height: '100%',
width: `${percentage.toString()}%`,
backgroundColor: 'red',
borderRadius: 'inherit',
textAlign: 'right'
}
const labelStyles = {
padding: 5,
color: 'white',
fontWeight: 'bold'
}
useEffect(() => {
const newPercentage = parseInt(percentage) + 1;
setInterval(() => setPercentage(newPercentage), 1000);
}, [percentage])
return (
<div style={containerStyles}>
<div style={fillerStyles}>
<span style={labelStyles}>{percentage}%</span>
</div>
</div>
)
}
export default Loading;
You should store your interval in a constant and use the cleanup function to clear the last interval each time.
I'd also change how you handle setPercentage and use timeout instead of interval
Something like this:
useEffect(() => {
const timeoutID = setTimeout(() =>
setPercentage(prevPercentage => prevPercentage + 1)
, 1000);
return () => clearTimeout(timeoutID);
}, [setPercentage]);

how to add a classList in react using material UI

I'm trying to add a class to an element using Material UI in a scroll event like this.
const useStyles = makeStyles({
sticky: {
position: 'fixed',
top: 0,
width: '100%',
}
});
export default function myBar() {
React.useEffect(() => {
const myBar = document.getElementById("myBar");
const sticky = myBar.offsetTop;
const scrolling = window.addEventListener("scroll", () => {
if (window.pageYOffset > sticky) {
myBar.classList.add("sticky");
} else {
myBar.classList.remove("sticky");
}
});
return () => {
window.removeEventListener("scroll", scrolling);
};
}, []);
const classes = useStyles();
return (
<header id="myBar">
// some content
</header>
);
};
The problem is that Mterial Ui will generate some random numbers after class name, like sticky_123 so it will never be only sticky
Is it any way I can solve this problem?
The problem is that Material Ui will generate some random numbers after class name, like sticky_123 so it will never be only sticky
In order to use the className generated by Material UI, you must use classes.sticky instead of "sticky".
By the way, the component name should be in PascalCase.
const useStyles = makeStyles({
sticky: {
position: 'fixed',
top: 0,
width: '100%',
}
});
export default function MyBar() {
const classes = useStyles();
useEffect(() => {
const myBar = document.getElementById("myBar");
const sticky = myBar.offsetTop;
const scrollHandler = () => {
if (window.pageYOffset > sticky) {
myBar.classList.add(classes.sticky);
} else {
myBar.classList.remove(classes.sticky);
}
};
window.addEventListener("scroll", scrollHandler)
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, [classes]);
return (
<header id="myBar">
// some content
</header>
);
};

react-native: How to create custom component dynamically and destroy it after use?

I am creating a screen in functional component where I have to execute an animation when there is any event occurs ... This event could occur 1000 times when screen is open ... so I have implemented a custom component which takes position on screen and animates ....
const FloatingComponent = (props) => {
const animationView = useSharedValue(1)
const animationOpacityView = useSharedValue(1)
const animationViewStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY: withTiming(animationView.value, {
duration: 3500
}),
}
],
opacity: withTiming(animationOpacityView.value, {
duration: 1500
})
}
})
useEffect(() => {
console.log('Component animation called')
animationView.value = -((Screen.width * 0.25))
animationOpacityView.value = 0
});
return (
<Animated.View style={[Styles.handImg, { top: props.topDistance }, animationViewStyle]}>
<Image
style={Styles.handImage}
source={require('../../../assets/images/hand.png')}
/>
</Animated.View>
);
};
To create it dynamically I implemented it like this
const driverFactory = (itemNumber) => {
console.log(itemNumber)
return (<FloatingComponent id={1} topDistance={(Screen.width * 0.25) * itemNumber} />);
};
but it never show up and executes ....
while if I add this
<FloatingDriver id={5} topDistance={(Screen.width * 0.25) * 6} />
to main screen return it always executes .... but by these I can not create n number of components at any time when i receive notification ...
I would have a parent component manage these n child components and their rendering. Here is an example showing how one might render all the components with animations and handle showing/removing them as needed.
import React from 'react';
import {Animated, Button, SafeAreaView, StyleSheet} from 'react-native';
const Particle = ({particle: {x, y}, onFinish}) => {
const opacity = React.useRef(new Animated.Value(0)).current;
React.useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 2000,
useNativeDriver: true,
}).start(() => {
onFinish();
});
}, []);
return (
<Animated.View
style={{
position: 'absolute',
left: x - 10,
bottom: y - 10,
width: 20,
height: 20,
backgroundColor: 'yellow',
opacity,
}}
/>
);
};
const ParticleSystem = () => {
const [layoutRectangle, setLayoutRectangle] = React.useState();
const [particleList, setParticleList] = React.useState([]);
const currentRef = React.useRef();
const addRandomParticle = React.useCallback(() => {
if (layoutRectangle) {
const newParticle = {
id: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER),
x: Math.random() * layoutRectangle.width,
y: Math.random() * layoutRectangle.height,
};
setParticleList([newParticle, ...particleList]);
}
}, [layoutRectangle, particleList]);
const removeParticle = React.useCallback(
(particle) => {
setParticleList(particleList.filter(({id}) => id !== particle.id));
},
[particleList],
);
currentRef.current = removeParticle;
return (
<SafeAreaView
style={styles.screen}
onLayout={(event) => setLayoutRectangle(event.nativeEvent.layout)}>
{particleList.map((particle) => (
<Particle
key={particle.id}
particle={particle}
onFinish={() => {
currentRef.current(particle);
}}
/>
))}
<Button title="Add particle" onPress={() => addRandomParticle()} />
</SafeAreaView>
);
};
export default ParticleSystem;
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: 'black',
},
});

Resources