Can you freeze the App until a (asynchronous) event has occured? - javascript

I want to show an AdMobInterstitial-Ad in my App before pushing to the next screen.
For now, I am using this method:
show the Ad (which takes 1-2 seconds to load)
pushing to the next screen
onpress={ () =>{
(title.slice(0, 4) === "X-01"
? navigation.push(...)
: navigation.push(...));
showAsyncAD();
}
}
The ad loads asynchroniously so the app pushes to the next screen, then shows the ad.
I want it to first load the ad then push to the next screen.
I tried this:
onpress={() =>
showAsyncAD().then(
() => title.slice(0, 4) === "X-01"
? navigation.push(...)
: navigation.push(...)
);
}
}
This does indeed work but until the ad comes up the user can press buttons which he is not allowed to. He can actually just press the button again and get to the screen before the ad starts to play.
How can I freeze the App until the ad comes up so the user wont do anything he is not allowed to?
If a loading circle is possible with this it would be the best option!
Thanks for any inputs!!!
EDIT: I want to make the whole App to freeze until the ad is shown

You can use state value which shows async action processing
const [processing, setProcessing] = useState(false);
setProcessing(true)
showAsyncAD().then(
() => {
setProcessing(false)
title.slice(0, 4) === "X-01"
? navigation.push(...)
: navigation.push(...)
})
And disable button(s) that you don't want to be clicked while async action is performed. for example;
<TouchableOpacity disabled={processing}>
</TouchableOpacity>
UPDATE
If you want to disable the whole screen, you can show a transparent overlay View on the screen. It will have the full width and height of the screen.
return (
<View style={{
position: 'relative',
...
}}>
// your views here
{processing && <View style={{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: 'transparent'
}} />}
</View>
)

Related

How to make react-native-reanimated-carousel work with ImageZoom (react-native-image-pan-zoom)?

I want to have a carousel that each item is a zoomable image, the carousel should be all over the screen so I use Portal for that. To support zoom I use ImageZoom component from react-native-image-pan-zoom, and the carousel is from react-native-reanimated-carousel, in the following way:
<Portal>
<Carousel
loop
windowSize={1}
width={SCREEN_WIDTH}
height={SCREEN_HEIGHT}
data={images}
style={{
flex: 1,
backgroundColor: "black",
}}
defaultIndex={imageIndexToDisplay}
onSnapToItem={handleImageChange}
renderItem={({ item: { url, width, height } }) => (
<ImageZoom
cropWidth={SCREEN_WIDTH}
cropHeight={SCREEN_HEIGHT}
imageWidth={width}
imageHeight={height}
enableSwipeDown
onSwipeDown={handleClose}
onClick={handlePress}
useNativeDriver
>
<Image
imageSource={url}
defaultImage={defaultImage}
imageStyle={{ height, width }}
/>
</ImageZoom>
)}
/>
</Portal>
What happens is that the carousel barely let me scroll left or right since it seems like the ImageZoom responds first to the scrolls. I tried to set onStartShouldSetPanResponder={() => false} on the ImageView which solves the Carousel scrolling but doesn't let me use the ImageZoom to zoom since it appears like the Carousel now responds first to gestures.
I would like to know if there is any way to make them both work together.
Thanks ahead!

How to change the speed of the color switch when button is clicked?

I made this code below that when you click the button the colors change. But I don't want the color change to happen instantly I want it to slowly change from one color to the next, like should take 5 seconds.
export default function App() {
const [colorIsRed,setColorIsRed] = React.useState(false);
return (
<View style={{flex:1,justifyContent:"center",alignItems:"center",backgroundColor:"black"}}>
<TouchableOpacity
style={{
width:100,
height:100,
borderRadius:20,
backgroundColor:colorIsRed?"red":"white",
borderWidth:1,
borderColor:colorIsRed?"white":"red",
}}
onPress={()=>{setColorIsRed(!colorIsRed)}}>
</TouchableOpacity>
</View>
);
}
What you need to do is take advantage of the Animated library in react-native. Here's a full example (https://snack.expo.dev/hXW2jw7yN). And below is the code explained with code comments.
export default function App() {
/**
* This is the animated value that keeps track of the
* progess of the animation
*/
const progress = React.useRef(new Animated.Value(0)).current;
/**
* Function to be called on button press
*/
const onPress = ()=>{
/**
* Runs a timing animation that moves the progress value to 1 or 0
* in 3 seconds
*/
Animated.timing(progress,{
toValue:progress._value===1?0:1, // If progress is 1 then set to 0 else set to 1
duration:3000, // Five seconds
}).start()
}
/**
* Uses the progess value to interpolate the color,
* this will "slowly change from one color to the next".
* Please search interpolate online and learn more
*
*/
const bgColor = progress.interpolate({
inputRange:[0,1],
outputRange:["white","red"]
})
const borderColor = progress.interpolate({
inputRange:[0,1],
outputRange:["red","white"]
})
return (
<View style={{flex:1,justifyContent:"center",alignItems:"center",backgroundColor:"black"}}>
<TouchableOpacity
onPress={onPress}>
<Animated.View style={{
width:100,
height:100,
borderRadius:20,
backgroundColor:bgColor,
borderColor:borderColor
}}>
</Animated.View>
</TouchableOpacity>
</View>
);
}
This may be silly, but have you tried adding:
transitionDuration: 5s
to style attribute?

React state resets/behaves badly when changing rapidly

Here I have a timer minute state and a scroll container, where the state value changes based on the scroll position and vice versa:
const Container = ({ minutes, setMinutes}) => {
draggableElement = useRef(null);
const handleScroll = () => {
const scrollableWidth =
draggableElement.current.container.current.scrollWidth -
draggableElement.current.container.current.offsetWidth;
const amountScrolled = draggableElement.current.scrollLeft;
const rangeValue = (amountScrolled / scrollableWidth) * 30;
setMinutes(Math.round(rangeValue));
}
useEffect(() => {
draggableElement.current.container.current.scrollLeft = minutes * 20;
}, [minutes]);
return (
<ScrollContainer
className="container"
ref={draggableElement}
onScroll={handleScroll}
id="scrolling-container"
vertical={false}
style={{
display: "grid",
gridTemplateColumns: "repeat(100, 0.25em)",
//border: "1px solid yellow",
width: "4em",
height: "50%",
alignItems: "center",
cursor: "grab",
marginBottom: "1rem",
}}
>
<Tick >1</Tick>
<Tick >2</Tick>
<Tick >3</Tick>
<Tick >4</Tick>
<Tick >5</Tick>
//more ticks here
</ScrollContainer>
);
};
on top of that, I also have counter buttons where you can increase/decrease the minute state value:
<AddButton onClick={()=> setMinutes(minutes + 1)}></AddButton>
<SubtractButton onClick={()=> setMinutes(minutes - 1)}></SubtractButton>
Now, everything seemed to be working just fine, until I tried to rapidly click the AddButton/SubtractButton. I have observed the following problems:
If I rapidly click it without using the scroll container yet, the state becomes NaN.
If I rapidly click it after scrolling to a specific position, the state value resets to the value based on the scroll position. Example: I scroll to position value 3, state value becomes 3. I click AddButton rapidly, state becomes 4 for a split second then resets to 3.
As I mentioned before, it works just fine if I click at a slow pace. Also, scrolling rapidly doesn't cause any problems.
How can I fix this?

Framer-motion drag not respecting previously updated props

A simple use-case is to allow a user to either click buttons to paginate in a slider, or drag. Both events call the same paginate function with a param to either go forward or back--simple stuff.
However, the trigger from drag seems to cause bizarre behavior where the slider wants to start the animation from several slides back as if it ignores the updated props. This doesn't happen when using the buttons and both use the same simple paginate call.
Any tips appreciated.
Minimal example:
export default function App() {
const [position, setPosition] = useState<number>(0);
const paginate = (direction: Direction) => {
setPosition((prev) => {
return direction === Direction.Forward
? Math.max(-800, prev - 200)
: Math.min(0, prev + 200);
});
};
return (
<div className="App">
<Slider>
<Wrapper
animate={{ x: position }}
transition={{
x: { duration: 1, type: "tween" }
}}
drag="x"
dragConstraints={{
top: 0,
left: 0,
right: 0,
bottom: 0
}}
onDragEnd={(e, { offset, velocity }) => {
const swipe = swipePower(offset.x, velocity.x);
if (swipe < -swipeConfidenceThreshold) {
paginate(Direction.Forward);
} else if (swipe > swipeConfidenceThreshold) {
paginate(Direction.Back);
}
}}
>
<Slide>1</Slide>
<Slide className="alt">2</Slide>
<Slide>3</Slide>
<Slide className="alt">4</Slide>
<Slide>5</Slide>
</Wrapper>
</Slider>
<button onClick={() => paginate(Direction.Back)}>prev</button>
<button onClick={() => paginate(Direction.Forward)}>next</button>
</div>
);
}
Codesandbox Demo
I have to say, this problem is quite interesting. However, I think I figured out a way for you to handle this. One thing I noticed is that if you comment out
onDragEnd={(e, { offset, velocity }) => {
// const swipe = swipePower(offset.x, velocity.x);
// if (swipe < -swipeConfidenceThreshold) {
// paginate(Direction.Forward);
// } else if (swipe > swipeConfidenceThreshold) {
// paginate(Direction.Back);
// }
}}
the entire onDragEnd prop function, this example still doesn't work, since by the looks of things, the draggable component is not respecting your offset.
I realized that at this point, the problem is the internal state of the component is out of sync with your state. And would you look at that, the Framer Motion API actually provides a way to inspect this.
https://www.framer.com/api/motion/motionvalue/#usemotionvalue
It's the hook useMotionValue() which allows us to see what's actually happening. Turns out, our value is being set wrong when the user starts dragging:
useEffect(
() =>
motionX.onChange((latest) => {
console.log("LATEST: ", latest);
}),
[]
);
We can see this, because the state "jumps" to 200 as soon as we start dragging.
So fixing in theory is easy, we just need to make sure to let that value "know" about our offset, and that way it's gonna start with the proper offset in mind!
Anyway, that was my thought process, here's the solution, all you need to do is set the left constraint to make it work:
dragConstraints={{
top: 0,
left: position,
right: 0,
bottom: 0
}}
And tada! This makes it work. Here's my working solution: https://codesandbox.io/s/lingering-waterfall-2tsfi?file=/src/App.tsx

Use a slider to scroll a horizontal scrollView in ReactNative

I am trying to set up a slider inside a horizontal ScrollView that would allow me to scroll the page faster. I am able to link position of the page to the value of the slider, so that when I scroll the page, the thumb of the slider moves accordingly.
I am using React Native Slider and a ScrollView.
Here is the result that I am unfortunately having.
I am quite new to RN, so I am probably missing something important here.
class Comp extends Component {
state = {
width : 0,
value : 0,
cursor : 0
}
moveTheCursor = (val) => {
this.scrollView.scrollTo({x: val, y: 0, animated: true})
}
scrollTheView = (event) => {
this.setState({
value : Math.floor(event.nativeEvent.contentOffset.x),
cursor : Math.floor(event.nativeEvent.contentOffset.x)
})
}
checkWidth = ({nativeEvent}) => {
arrayWidth.push(nativeEvent.layout.width)
let ww = (arrayWidth[0] * this.props.data.length) - Math.round(Dimensions.get('window').width);
this.setState({
width : ww,
})
}
render() {
return (
<React.Fragment>
<ScrollView
ref={ref => this.scrollView = ref}
horizontal
style={styles.car}
scrollEventThrottle={1}
onScroll={this.scrollTheView}
showsHorizontalScrollIndicator={false}
decelerationRate={0}
//snapToInterval={200} //your element width
snapToAlignment={"center"}
>
</ScrollView>
<Slider
style={styles.containerSlide}
thumbImage={require("./../../assets/img/buttons/thumb.png")}
trackImage={require("./../../assets/img/buttons/bar.png")}
minimumValue={0}
maximumValue={this.state.width}
onValueChange={this.moveTheCursor}
value={this.state.cursor}
/>
</React.Fragment>
);
}
}
The problem is, when I use the thumb of the slider to scroll the page, it triggers the scroll that inevitably resets the position of the slider thumb, so it is not behaving correctly (flickers but it is mostly inaccurate).
Is there a way to fix this loop?
You can achieve desired behaviour using Slider's onSlidingStart and onSlidingComplete + ScrollView's scrollEnabled
It can look like
....
const [isSliding, setIsSliding] = useState(false);
....
<ScrollView scrollEnabled={!isSliding}>
<Slider
onSlidingStart={() => setIsSliding(true)}
onSlidingComplete={() => setIsSliding(false)}
/>
</ScrollView>

Categories