How to transition mui background color string? - css

I have the following component where I want to change the color depending on user type , issue is when I use gradient ... the color get stops from transitioning , if I use simple colors like bule , red , green .... then it works but at the current state in code the colors changes but whiteout the slowed transition ... how to solve this ?
const Home: React.FC = () => {
const _authContext = useContext(authContext);
const hexUserArr = ['linear-gradient(360deg, #fe6b8b 30%, #7DF9FF 70%)'];
const hexAdminArr = ['linear-gradient(360deg, #dfe566 30%, #7DF334 70%)'];
return (
<div
style={{
minHeight: '100vh',
marginTop: 0,
marginBottom: -50,
justifyContent: 'center',
background: _authContext.starterUserType === 'admin' ? hexAdminArr[0] : hexUserArr[0],
transition: 'background 10s ease',
}}
>
</div>
);
};
export default Home;

It's how React works, you should rewrite following lines
const hexUserArr = ['linear-gradient(360deg, #fe6b8b 30%, #7DF9FF 70%)'];
const hexAdminArr = ['linear-gradient(360deg, #dfe566 30%, #7DF334 70%)'];
with React.useState hook
const [hexUserArr, setHexUserArr] = React.useState([...])

Or if first answer didn't work, look here, that is how I dealt mine:
const getStyle = () => {
if(user !== null)
{
const deg = -135 + Math.round(360 * user.vacationsNum / defaultNumVacations / 15) * 15;
return {
ROTATE: deg
}
}
}
const PROGRESS_LAYER = user ? {
width: "5rem",
height: "5rem",
transform: `rotate(${getStyle().ROTATE}deg)`
} : "";
return (
<div style={PROGRESS_LAYER} />
)

Related

View centered within the absolutely positioned Animated.View inside a ScrollView shifts position

The code to render a TabList:
import React, { Children, useEffect } from 'react';
import { LayoutChangeEvent, View } from 'react-native';
import {
ScrollView,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { isValidChild } from '#utils';
import { useTabIndex } from '../tab-context';
import { useStyle } from './tab-list.styles';
import { TabListProps } from './tab-list.type';
const animConfig = {
duration: 200,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
};
const TabList: React.FC<TabListProps> = props => {
const styles = useStyle();
const { children, onChange } = props;
const selectedTabIndex = useTabIndex();
const animatedTabIndicatorPosition = useSharedValue(0);
// Save layout of the container
const [containerLayout, setContainerLayout] = React.useState({
x: 0,
y: 0,
width: 0,
height: 0,
});
const onContainerLayout = (event: LayoutChangeEvent) => {
const { x, y, width, height } = event.nativeEvent.layout;
setContainerLayout({ x, y, width, height });
};
// get children length
const childrenLength = Children.count(children);
const tabWidth =
childrenLength > 3
? containerLayout.width / 3
: containerLayout.width / childrenLength;
const renderChildren = () => {
// Render only children of component type TabList
return Children.map(children, child => {
// Check if child is a valid React element and has type TabList
if (isValidChild(child, 'Tab')) {
return (
<TouchableWithoutFeedback
containerStyle={{ width: tabWidth }}
onPress={() => onChange((child as JSX.Element)?.props.tabIndex)}
>
{child}
</TouchableWithoutFeedback>
);
}
// Throw error if child is not a TabList
throw new Error('TabList component can only have children of type Tab');
});
};
useEffect(() => {
animatedTabIndicatorPosition.value = selectedTabIndex * tabWidth;
}, [selectedTabIndex]);
const indicatorAnimatedStyle = useAnimatedStyle(() => {
return {
width: tabWidth,
transform: [
{
translateX: withTiming(
animatedTabIndicatorPosition.value,
animConfig,
),
},
],
};
}, []);
return (
<View onLayout={onContainerLayout} style={styles.container}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
testID="TestID__component-TabList"
>
<Animated.View
style={[styles.indicatorContainer, indicatorAnimatedStyle]}
>
<View
style={[
styles.indicator,
{
width: tabWidth - 4,
},
]}
/>
</Animated.View>
{renderChildren()}
</ScrollView>
</View>
);
};
export default TabList;
The styles for the component elements:
import { createUseStyle } from '#theme';
// createUseStyle basically returns (fn) => useStyle(fn)
export const useStyle = createUseStyle(theme => ({
container: {
position: 'relative',
flexGrow: 1,
backgroundColor: theme.palette.accents.color8,
height: 32,
borderRadius: theme.shape.borderRadius(4.5),
},
indicatorContainer: {
position: 'absolute',
height: 32,
justifyContent: 'center',
alignItems: 'center',
},
indicator: {
height: 28,
backgroundColor: theme.palette.background.main,
borderRadius: theme.shape.borderRadius(4),
},
}));
I am using react-native-reanimated to animate the tab indicator. What I noticed is, on app reload, the initial tab indicator position keeps on changing as seen in the GIF I have attached. At times, it is positioned where it should be and at times, half the box is hidden behind the scrollview container. When I remove the alignItems: center from the Animated.View, things work as expected.
I am perplexed as to why the position keeps changing because of align-items?
The issue was that the child indicator component wasn't wrapped within the boundary of the indicator container. I resolved this by adding flexWrap: 'wrap' to the parent indicator container.
So, the new style looks like this:
import { createUseStyle } from '#theme';
// createUseStyle basically returns (fn) => useStyle(fn)
export const useStyle = createUseStyle(theme => ({
container: {
position: 'relative',
flexGrow: 1,
backgroundColor: theme.palette.accents.color8,
height: 32,
borderRadius: theme.shape.borderRadius(4.5),
},
indicatorContainer: {
position: 'absolute',
height: 32,
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap'
},
indicator: {
height: 28,
backgroundColor: theme.palette.background.main,
borderRadius: theme.shape.borderRadius(4),
},
}));
Edit: disregard the below, the behavior is not due to hot reloading! I'll leave this up in case anyone else has the same misconception
Hot reloading is not reliable with Reanimated - there are values on native threads that won't get refreshed. This has no impact on the final app.
To test whether it's really working, simply shake the device/sim and hit Reload after you make changes. This is enough to clear any sticky values. If your component still isn't doing what you want, you can then have the confidence to edit it and be sure it looks right.

set Style using React and Hooks

I have been using React JS for a short time. Looking at the following example, I would like to change the background color only on the clicked lines and not all lines. Actually, clicking on a row turns it red and clicking again makes the row background white. Before changing color, I would set all lines to default color states, but something is wrong.
import { useState } from "react";
export default function App() {
const [color, setColor] = useState("white");
const handleClick = (e) => {
const value_color = e.target.style.backgroundColor;
if (value_color != "" && value_color != undefined) {
if (value_color === "white")
var new_color = "red";
else
var new_color = "white";
}
setColor("white");
e.target.style.backgroundColor = new_color;
};
return (
<div>
<div
id={"line1_id"}
style={{
backgroundColor: color
}}
onClick={handleClick}
>
First Line
</div>
<div
id={"line2_id"}
style={{
backgroundColor: color
}}
onClick={handleClick}
>
Second Line
</div>
</div>
);
}
EDIT:
Maybe I have explained the problem wrong. Actually my code works, but clicking on both I get two red lines. In general, when I click on a new line, the other line should go white again. Thank you
Create JSON for the data and use map for rendering the data and updating the backgroundColor
import { useState } from 'react';
export default function App() {
const [data, setData] = useState([
{ id: 1, text: 'First Line', color: 'white' },
{ id: 2, text: 'Second Line', color: 'white' }
]);
const handleClick = item => {
setData(data =>
data.map(n => {
return {
...n,
color: n.id === item.id && n.color === 'white' ? 'red' : 'white'
};
})
);
};
return (
<div>
{data.map(item => (
<div
id={item.id}
key={item.id}
style={{
backgroundColor: item.color
}}
onClick={() => handleClick(item)}
>
{item.text}
</div>
))}
</div>
);
}
here is the Codesandbox example
The new_color value is not defined because you are setting it in the if-else statements. So, only code in the if-else blocks can access them. You can declare a variable and assign a default value outside them and then reassign it:
// some other code
let new_color = "white"
if (value_color != "" && value_color != undefined) {
if (value_color === "white")
new_color = "red";
else
new_color = "white";
}
// some code
e.target.style.backgroundColor = new_color;

How to land on the image a user has clicked in image swipe?

My React Native 0.64 app uses react-native-gesture-handler 1.16/react-native-reanimated 2.1 to swipe images left and right. The idea is when user clicks a image in image gallery, then the image was displayed occupying the full screen and the rest of images in the gallery can be viewed one by one by swipe left or right. Here is the full code which swipe works but with one catch: the image clicked is not presented but the first image in gallery is always shown first. How to land on the image a user has clicked?
import React, { useRef } from "react";
import { Dimensions, StyleSheet, View, Platform } from "react-native";
import FastImage from 'react-native-fast-image';
import Animated, {
useAnimatedStyle,
useSharedValue,
useAnimatedRef,
useAnimatedGestureHandler,
useAnimatedReaction,
withTiming,
withSpring,
useDerivedValue,
} from "react-native-reanimated";
import {
snapPoint,
} from "react-native-redash";
import {
PanGestureHandler,
PinchGestureHandler,
State,
TouchableOpacity,
} from "react-native-gesture-handler";
const { width, height } = Dimensions.get("window");
export default Swipe = ({route, navigation}) => {
const {images, initIndex} = route.params; //initIndex is the index of image clicked
console.log("route params swipe ", route.params);
const index = useSharedValue(initIndex);
const snapPoints = images.map((_, i) => i * -width);
const panRef = useAnimatedRef();
const pinchRef = useAnimatedRef();
const offsetX = initIndex ? useSharedValue(initIndex * -width) : useSharedValue(0); //The init offsetX is set according to initIndex a user clicked.
const translationX = useSharedValue(0);
const translateX = useSharedValue(0); //if translateX was assgined with offsetX, then swipe stops working
//for snap in swipe
const lowVal = useDerivedValue(() => {return (index.value + 1) * -width}); //shared value, with .value
const highVal = useDerivedValue(() => {return (index.value - 1) * -width}); //shared value, with .value
const snapPt = (e) => {
"worklet";
return snapPoint(translateX.value, e.velocityX, snapPoints);
};
const panHandler = useAnimatedGestureHandler(
{
onStart: () => {
translateX.value = offsetX.value; //As soon as a user swipe, this line allow the
//image is moved to initIndex-th image. But how to land on the image a user has clicked?
},
onActive: (event) => {
translateX.value = offsetX.value + event.translationX;
translationX.value = event.translationX;
},
onEnd: (event) => {
let val = snapPt(event);
let sp = Math.min(Math.max(lowVal.value, val), highVal.value); //clamp in RN redash
if (event.translationX !== 0) {
translateX.value = withTiming(sp);
offsetX.value = translateX.value;
};
//update index
index.value = Math.floor(translateX.value/-width);
},
}, []
)
const swipeStyle = useAnimatedStyle(() => {
return {
transform: [{ translateX: translateX.value}]
}
});
return (
<PanGestureHandler
onGestureEvent={panHandler}
ref={panRef}
minDist={10}
avgTouches
>
<Animated.View style={styles.container}>
<Animated.View
style={[{width: width * images.length, height, flexDirection: "row"}, swipeStyle]}
>
{images.map((img_source, i) => {
const isActive = useDerivedValue(() => {return index.value === i});
return (
<View key={i} style={styles.picture}>
<View style={[
styles.image,
]}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.button}>
<View style={styles.button}>
<FastImage
source={{uri:img_source.path}}
resizeMode={FastImage.resizeMode.contain}
style={styles.image}
/>
</View>
</TouchableOpacity>
</View>
</View>
);
})}
</Animated.View>
</Animated.View>
</PanGestureHandler>
);
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
backgroundColor: "black",
},
picture: {
width,
height,
overflow: "hidden",
},
image: {
//...StyleSheet.absoluteFillObject,
flex:1,
width: undefined,
height: undefined,
//resizeMode: "cover",
},
button: {
width: width,
height:height,
},
});
Another issue the code is that when touching the image before swipe the image jerks left or right 1/3-1/2 screen width. This makes the swipe un-smooth. I would like to know how to eliminate the image jerk and make the swipe smooth from beginning to the end.
To land on the image a user has clicked, a useEffect hook is added :
useEffect(() => {
translateX.value = offsetX.value;
}, [initIndex]);
The onStart needs to be removed completely.
The reason for image jerking is caused by misrepresenting of offsetX value. The offsetX shall be at the border of the screen (multiple of the screen width). However when the image width is less than the screen width, the offsetX may be at the border of the image which is a little less or more (depending on if swipe left or swipe right) and this causes the image jerking left or right when swiping initially. In this case, the offsetX needs to be set to the border of the screen (multiple of screen width).

ReactDOM.createPortal() is creating extra blank divs in next.js-typescript

this is backdrop.tsx:
interface BacdropProps {
open?: string;
onClick: () => void;
}
const Backdrop: React.FC<BacdropProps> = (props) => {
let container: HTMLDivElement | null = null;
if (typeof window !== "undefined") {
const rootContainer = document.createElement("div");
const parentElem = document.querySelector("#__next");
parentElem?.insertAdjacentElement("afterend", rootContainer);
// parentElem?.after(rootContainer) this gives me same issue
container = rootContainer;
}
return container
? ReactDOM.createPortal(
<div
className={["backdrop", props.open ? "open" : ""].join(" ")}
onClick={props.onClick}
/>,
container
)
: null;
};
export default Backdrop;
this is css for Backdoor.tsx
.backdrop {
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
z-index: 100;
position: fixed;
left: 0;
top: 0;
transition: opacity 0.3s ease-out;
opacity: 1;
}
this is how it looks:
Your code will create div.backdrop every time when Backdrop re-render. The correct way should be create it once. The correct way is using useEffect to promise ReactDOM.createPortal just be executed once. And also apply the useRef to make sure container to keep the same instance in every render.
const containerRef = useRef<HTMLDivElement>(null);
useEffect({
// Will be execute once in client-side
if (typeof window !== "undefined") {
const rootContainer = document.createElement("div");
const parentElem = document.querySelector("#__next");
parentElem?.insertAdjacentElement("afterend", rootContainer);
// parentElem?.after(rootContainer) this gives me same issue
containerRef.current = rootContainer;
}
}, [window])
useEffect({
// Will be execute once when containerRef is bind to <HTMLDivElement>
if(containerRef.current) {
ReactDOM.createPortal(
<div
className={["backdrop", props.open ? "open" : ""].join(" ")}
onClick={props.onClick}
/>,
containerRef.current
)
}
}, [containerRef])
Edit
I removed the detection of existence in window, since useEffect would be executed only in client-side.
Since ReactDOM.createPortal will create the div.backdrop outside of the root HTMLElement (div#next), i think just return null in Backdrop component is fine.
const containerRef = useRef<HTMLDivElement>(null);
useEffect({
// useEffect would run just in client-side
const rootContainer = document.createElement("div");
const parentElem = document.querySelector("#__next");
parentElem?.insertAdjacentElement("afterend", rootContainer);
// parentElem?.after(rootContainer) this gives me same issue
containerRef.current = rootContainer;
}, [])
useEffect({
// Will be execute once when containerRef is bind to <HTMLDivElement>
if(containerRef.current) {
ReactDOM.createPortal(
<div
className={["backdrop", props.open ? "open" : ""].join(" ")}
onClick={props.onClick}
/>,
containerRef.current
)
}
}, [containerRef])
return null;

How to add animation to a view changing its height in react native?

I am trying to increase and decrease the height of a rectangle depending on several conditions... The task is completed, however, I want to make the height changes smoothly and gradually.
How could I do that in react native while my CSS is dynamically changing?
getBarStyle() {
if (this.state.value < this.state.minValue) {
return {
height: 0
};
}
let accurateHeight;
accurateHeight = (this.state.value * (this.state.graphHeight - lineDiffValue);
return {
height: accurateHeight,
transition: "height 2s"
};
}
render() {
return (
<View style={[styles.rectangle, this.getBarStyle()]} />
)
}
Firstly, create an Animated.Value with the starting value of the View.
constructor() {
this.animatedValue = new Animated.Value(50);
}
Then, create a function which will animated the height. The example below will animate from 50 to 100.
startAnimation = () => {
Animated.timing(this.animatedValue, {
toValue: 100
}).start();
}
Finally, pass your animated style to your View. You need to use Animated.View, not View.
render() {
return (
<Animated.View style={{ height: this.animatedValue }} />
)
}
Don't forget to import { Animated } from 'react-native';
Error
<Animated.View style={{ height: this.animatedValue }} />
error: Style property 'height' is not supported by native animated module.
As the React-Native documentation said, you can only animate non-layout properties. Transform property is supported so you can use transform.scaleY instead of changing the height :((
so..
Try my solution
constructor(props: any)
{
this.state.isDisplay = true;
this.state.animatedView = new Animated.Value(100);
this.state.animatedViewInterpolate =
this.state.animatedView.interpolate({ inputRange: [0, 100], outputRange: ["0%", "100%"] });
this.animateDuration = 500;
}
onPressDisplayView()
{
if (this.state.isDisplay)
{
Animated.timing(this.state.animatedView, { toValue: 0, duration: this.animateDuration }).start(() =>
{
this.setState({ isDisplay: false });
});
}
else
{
Animated.timing(this.state.animatedView, { toValue: 100, duration: this.animateDuration }).start(() =>
{
this.setState({ isDisplay: true });
});
}
}
render()
{
return(
<View style={{flex: 1}}>
<Button onPress={() => {this.onPressDisplayView()}}>Display</Button>
<View style={{ height: this.state.animatedViewInterpolate, backgroundColor: '#F00' }}>
<Text>HelloWorld!</Text>
</View>
</View>);
}
use interpolate
I wrote this without running. maybe work or not :)

Resources