react-native-reanimated useSharedValue does not update in jest tests - react-native

I'm currently trying to figure out how to test reanimated 2 animations using useSharedValue.
What makes 0 sense to me is the example given from reanimated.
https://github.com/software-mansion/react-native-reanimated/blob/master/tests/SharedValue.test.js
If the button is supposed to increment its sharedValue by 1 every time you press it. Why would you write a test that shows it does NOT change???
I've tried it myself and yea, the value does not update itself.
I want to assert that the value has changed in my test:
ParallaxScrollView.tsx
const scrollY = useSharedValue(0);
const onScroll = useAnimatedScrollHandler((event) => {
scrollY.value = event.contentOffset.y;
});
return (
<Animated.Image
style={{height: scrollY}}
testID="header-image"
source={{ uri: headerImage }}
resizeMode="cover"
/>
)
ParallaxScrollView.test.tsx
const { getByTestId } = render(<ParallaxScrollView {...defaultProps} />);
const headerImage = getByTestId('header-image');
const content = getByTestId('parallax-content');
const eventData = {
nativeEvent: {
contentOffset: {
y: 100,
},
},
};
fireEvent.scroll(content, eventData);
expect(headerImage).toHaveAnimatedStyle({ height: 100 }); //Received is 0

useAnimatedScrollHandler uses react-native-gesture-handler to handle events but at this moment gesture-handler doesn't support events in tests yet, this is what I am working on. Look at this - https://github.com/software-mansion/react-native-gesture-handler/pull/1762
I think this will be available soon. If you want to be up to date, please open an issue in the Reanimated Github.

Related

In React Native, creating a canva and calling getcontext create error

I am trying to use a function called readpixels from this github page and in this function one need to get the context of a canva, but since I am using React Native, I cannot use expressions like new Image() or document.createElement('canvas') so I am trying to do an equivalent using React Native functions.
Here is a minimal version of the code:
import React, { useState, useEffect, useRef } from 'react';
import { Button, Image, View } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import Canvas from 'react-native-canvas';
export function Canva() {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
const ctx = ref.current.getContext('2d');
if (ctx) {
Alert.alert('Canvas is ready');
}
}
}, [ref]);
return (
<Canvas ref={ref} />
);
}
function readpixels(url, limit = 0) {
const img = React.createElement(
"img",
{
src: url,
},
)
const canvas = Canva()
const ctx = canvas.getContext('2d')
return 1
}
export default function ImagePickerExample() {
const [image, setImage] = useState(null);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
quality: 1,
});
readpixels(result.uri)
if (!result.cancelled) {
setImage({uri: result.uri, fileSize: result.fileSize});
}
};
return (
<View style={{ flex: 1, backgroundColor: "white", marginTop: 50 }} >
<Button title="Pick image from camera roll" onPress={pickImage} />
{image && <Image source={{ uri: image.uri }} style={{ width: 200, height: 200 }} />}
</View>
);
}
And here is the error that I get:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I have checked the three suggestions to solve the issue, but it did not work.
Thank you
p.s: in order to reproduce the code, you would need to install the react-native-canvas package

Pass useAnimatedGestureHandler via forwardRef

I'm about to swap the old React Native Animated library with the new React Native Reanimated one to gain performance issues but I have encountered one problem I could not solve.
In all examples I found online, I saw that the GestureHandler, created with useAnimatedGestureHandler, is in the same component as the Animated.View. In reality that is sometimes not possible.
In my previous app, I just pass the GestureHandler object to the component via forwardRef but it seems React Native Reanimated is not able to do that. I don't know whether I have a syntax error or it is just a bug.
const App = () => {
const handlerRef = useAnimatedRef();
const y = useSharedValue(0);
handlerRef.current = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.startY = y.value;
},
onActive: ({translationX, translationY}, ctx) => {
y.value = translationY;
},
onEnd: () => {},
});
const animatedStyles = useAnimatedStyle(() => ({transform: [{translateY: withSpring(y.value)}]}));
const UsingHandlerDirect = () => (
<PanGestureHandler onGestureEvent={handlerRef.current} >
<Animated.View style={[styles.blueBox, animatedStyles]} />
</PanGestureHandler>
)
const UsingHandlerForwardRef = forwardRef(({animatedStyles}, ref) => (
<PanGestureHandler onGestureEvent={ref?.handlerRef?.current}>
<Animated.View style={[styles.redBox, animatedStyles]} />
</PanGestureHandler>
));
return (
<SafeAreaView>
<View style={styles.container}>
<UsingHandlerForwardRef ref={handlerRef} animatedStyles={animatedStyles}/>
<UsingHandlerDirect />
</View>
</SafeAreaView>
);
}
I have saved the GestureHandler in a useAnimatedRef handlerRef.current = useAnimatedGestureHandler({}) to make things more representable. Then I pass the the ref directly into the PanGestureHandler of the UsingHandlerDirect component. The result is that when I drag the blue box the box will follow the handler. So this version works.
But as soon as I pass the handlerRef to the UsingHandlerForwardRef component non of the gesture events get fired. I would expect that when I drag the red box will also follow the handler but it doesn't
Has someone an idea whether it's me or it's a bug in the library?
Cheers
I have given up on the idea to pass a ref around instead, I created a hook that connects both components with each other via context.
I created a simple hook
import { useSharedValue } from 'react-native-reanimated';
const useAppState = () => {
const sharedXValue = useSharedValue(0);
return {
sharedXValue,
};
};
export default useAppState;
that holds the shared value using useSharedValue from reanimated 2
The child component uses this value in the gestureHandler like that
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.startX = sharedXValue.value;
},
onActive: (event, ctx) => {
sharedXValue.value = ctx.startX + event.translationX;
},
onEnd: (_) => {
sharedXValue.value = withSpring(0);
},
});
and the Parent just consumes the hook value
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: -sharedXValue.value,
},
],
};
});
I have created a workable Snack which contains the 2 components - a Child with a blue box and a Parent with a red box

How to set up a ProgressBar for certain Interval - React Native(Should support IOS and Android)

In my app I need to show Progress Bar, that progress should be increase from 0 to 1 minutes or given Max time.
Tried - ONE
I tried a Progress Bar from 'react-native-paper'
<ProgressBar progress={getTime} color={COLORS.grey} />
Tried - TWO
<Animated.View
style={[
{
width: '100%',
height: 5,
borderRadius: 1,
backgroundColor: COLORS.yellow,
},
{width: 25 + '%'},
]}
/>
But not able to get the output
Let me know to achieve this like in the Image below. Thank You
For both solutions you tried you need to calculate the progress value for the component. In case of react-native-paper ProgressBar, this value should be in the range of 0-1. In case of you Animated.View solution, it should be in the range of 0-100.
To calculate this value, you need to know total time and how much time has passed. You can then simply divide elapsedTime/totalTime to get a value between 0-1.
If you need to track progress from 0 to 5 minutes, and want it to update every 1s:
const useProgress = (maxTimeInSeconds = 300) => {
const [elapsedTime, setElapsedTime] = useState(0);
const [progress, setProgress] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
if (progress < 1) {
setElapsedTime(t => t + 1);
}
}, 1000);
return () => clearInterval(intervalId);
}, []);
useEffect(() => {
setProgress(elapsedTime / maxTimeInSeconds);
}, [elapsedTime]);
return progress;
};
How to use, in a component:
const progress = useProgress();
return <ProgressBar progress={progress} />
Note: code not tested. This will start progress automatically when useProgress is called.

setTimeout in Hooks is not changing state

I am trying to upload image and after 20 seconds, if image is not uplaoded I want to close UploadingIndicator component (which appeared with the start of uploading) and display custom WaitAlert component, where user can choose to wait or try to send image again.
But I have problem with displaying custom WaitAlert component. It is not displayed.
Here is my code:
import WaitAlert from '../components/alert/waitAlert'
import UploadingIndicator from '../components/uploadingIndicator/uploadingInddicator'
let sendingTimeout
const App = (props) => {
const [showUploadingIndicator, setShowUploadingIndicator] = React.useState(false)
const [showWaitAlert, setShowWaitAlert] = React.useState(false);
saveAsset = () => {
setShowUploadingIndicator(true)
sendingTimeout = setTimeout(() => {
setShowUploadingIndicator(false)
setShowWaitAlert(true) //THIS IS NOT DISPLAYING WaitAlert
}, 20000)
updateMutation({
variables: {
assets: [{
files
}]
}
}).then(result => {
setShowUploadingIndicator(false)
clearTimeout(sendingTimeout)
setShowWaitAlert(false)
})
.catch((error) => {
console.log("error.toString()", JSON.stringify(error))
})
}
return (
<View>
<TouchableOpacity onPress={saveAsset} />
{showUploadingIndicator &&
<UploadingIndicator />
}
{showWaitAlert &&
<WaitAlert />
}
</View>
);
}
How can I make WaitAlert appeared?
The sendingTimeout is been cleared in clearTimeout(sendingTimeout), then the setTimeout is not triggered after 20 seconds. Try to remove the line clearTimeout(sendingTimeout) to see if it works.
I found the solution.
The problem was in WaitAlert component.
I added style of main View, where modal was placed to:
mainView: {
top: 0,
left: 0,
flex: 1,
position: 'absolute',
zIndex: 10,
},

React native: Avoid screen going light before blur effect

I have a background image, and I want to blur it out more and more.
So far the blurring works fine:
export default class Example extends Component {
state = { blurState: 1 };
componentWillMount = () => {
this.getRandomBlurIntervals();
}
getRandomBlurIntervals = () => {
var blurState = this.state.blurState;
setInterval(() => {
blurState = blurState + Math.random();
this.setState({blurState});
}, 2000);
}
render() {
return (
<ImageBackground source={require('../images/awesomeImage.jpg')}
blurRadius = {this.state.blurState}
style={styles.backgroundImage}>
<Text >Some awesome text</Text>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
backgroundImage: {
flex: 1,
width: null,
height: null
}
});
My problem is, that when changing from blurry state to the next one, the screen goes light for a moment, like it's reloading the page. I do not need a super smooth transition from one blur state to the other, but I want to get rid of these white flashed screens.
Is there any setting that helps avoiding them?
(I don't know if react-native-blur would solve this better, but I am currently using expo, so I cannot use that one.)