setState change unreliable in changing object properties - react-native

I'm trying to make a custom play button appear when a video is paused. The play button disappears and reappears when it is clicked directly (expected behavior), but not when I use the video's "native controls" though the value of the corresponding state variable does change. What am I doing wrong?
import React, { Component } from 'react';
import { View, TouchableOpacity, StyleSheet, Text } from 'react-native';
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
import { AsyncStorage } from 'react-native';
import { Audio, Video } from 'expo-av';
import { Ionicons } from '#expo/vector-icons';
export class VideoControl extends React.Component {
constructor (props) {
super(props);
this.playButton = React.createRef();
console.log("props props props");
this.video = null;
this.state = {
"vidPlaying": true,
"playOpacity": 1,
"playbackObject": null
}
}
_handleVideoRef = component => {
this.video = component;
}
_playState = playStatus => {
// console.log(playStatus["isPlaying"]);
if (playStatus.isPlaying == true && this.state.vidPlaying == false) {
console.log("playing is ");
activateKeepAwake();
this.setState({"vidPlaying": true});
console.log(this.state.vidPlaying);
}
else if (playStatus.isPlaying == false && this.state.vidPlaying == true) {
console.log("paused");
deactivateKeepAwake();
this.setState({"vidPlaying": false});
console.log(this.state.vidPlaying);
}
// console.log(this.state.vidPlaying);
}
playVideo = async () => {
if (this.video !== null) {
var stat = await this.video.getStatusAsync();
if (stat.isPlaying == true) {
console.log("playing")
this.video.pauseAsync();
}
else {
console.log("not playing")
if (stat.positionMillis == stat.playableDurationMillis) {
this.video.replayAsync();
}
else {
this.video.playAsync();
}
}
}
return true;
}
render(){
return(
<View>
<Video
source={{ uri: this.props.uri }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode={Video.RESIZE_MODE_CONTAIN}
useNativeControls={true}
shouldPlay={false}
isLooping={false}
style={{ height: 400 }}
ref={this._handleVideoRef}
onPlaybackStatusUpdate={this._playState}
/>
<View style={{
alignSelf: "center",
flex:1,
flexDirection: "row",
height: "100%",
width: "100%",
justifyContent: "space-around",
alignItems: "center",
flex: 1,
position: "absolute"}}>
<TouchableOpacity
ref={this.playButton}
style={{
backgroundColor: 'rgba(52, 52, 52, 0.4)',
width: 80,
height: 80,
borderRadius:80,
justifyContent: "space-around",
opacity: this.state.vidPlaying? 1:0,
alignItems: "center"
}}
underlayColor="#111" delayPressIn={0} delayPressOut={10} onPress={this.playVideo}>
<Ionicons name="ios-play"
style={{
textShadowColor: '#333',
textShadowOffset: {width: -1, height: 1},
textShadowRadius: 10}}
size={54}
color="white" />
</TouchableOpacity>
</View>
<Text style={{flex: 1, fontSize: 30, position: "absolute"}}> Terst {this.state.vidPlaying? 1:0} </Text>
</View>
)
}
}
The console.log inside the _playState handler prints the right vidPlaying value as expected (though sometimes it switches rapidly). At the same time, even in expected behavior, somehow the value of vidPlaying being passed to the TouchableHighlight is really the opposite of what should be going. The button is visible when my test text shows 0, and vice versa. The text changes when the state changes, either from the touchablehighlight click or the nativecontrols play, but the touchablehighlight doesn't always.

Related

Change component style when state is set to certain value

I have a counter that counts down, when the counter hits zero, I would like a style to change. The counter displays properly but the style never changes. How can I get the style to change when the counter hits a certain value?
Below is the code for a very basic example.
import React, { useState, useEffect } from "react";
import { Text, StyleSheet, SafeAreaView, TouchableOpacity, View } from "react-native";
const ActionScreen = () => {
const [timeLeft, setTimeLeft] = useState(4);
const [opac, setOpac] = useState(0.8);
useEffect(() => {
const interval = setInterval(() => setTimeLeft(timeLeft - 1), 1000);
if (timeLeft > 2) {
setOpac(0.8);
console.log("GT");
} else {
setOpac(0.5);
console.log("LT");
}
console.log("opac: ", opac);
if (timeLeft === 0) {
// clearInterval(interval);
setTimeLeft(4);
// setOpac(0.5);
}
return () => {
clearInterval(interval);
};
}, [timeLeft]);
return (
<SafeAreaView style={styles.container}>
<View style={styles.subActionContainer}>
<TouchableOpacity
style={[
styles.topButtons,
{
opacity: opac,
}
]}
>
<Text
style={styles.buttonText}>
{timeLeft}
</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 25,
backgroundColor: 'black',
},
subActionContainer: {
flexDirection: "row",
alignContent: 'space-between',
},
topButtons: {
flex: 1,
backgroundColor: 'orange',
},
buttonText: {
fontSize: 18,
textAlign: 'center',
padding: 10,
color: 'white',
},
buttonFail: {
borderWidth: 1,
marginHorizontal: 5,
}
});
export default ActionScreen;
I'm fairly new to React-Native but read somewhere that some styles are set when the app loads and thus don't change but I have also implemented a Dark Mode on my app that sets inline styling, which works well. Why isn't the styling above changing?
Thank you.
you can create your timer and create a method isTimeOut and when it is true you can change the background
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import {useTimerCountDown} from './useCountDownTimer'
export default function App() {
const {minutes, seconds, setSeconds} = useTimerCountDown(0, 10);
const isTimeOut = Boolean(minutes === 0 && seconds === 0);
const handleRestartTimer = () => setSeconds(10);
return (
<View style={styles.container}>
<View style={[styles.welcomeContainer,isTimeOut &&
styles.timeOutStyle]}>
<Text>welcome</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: 10,
backgroundColor: '#ecf0f1',
padding: 8,
},
welcomeContainer:{
backgroundColor:'red',
},
timeOutStyle:{
backgroundColor:'blue',
}
});
See the useTimerCountDown hook here in this
working example

How view saved data by AsyncStorage?

I have a code that saves and reads the value of a state through AsyncStorage, however when closing the app or changing the screen the value returns to the original state value, which in my case is zero. How do I change the screen or close the app so the value remains the last changed? What alternative to state variables?
My code is:
import React, { Component } from 'react'
import { Text, View, AsyncStorage, TextInput, StyleSheet, TouchableOpacity } from 'react-native'
export default class App extends Component {
constructor(props){
super(props);
this.state = {
txtInputData: '',
getValue: '',
}
}
saveValue = () => {
if(this.state.txtInputData){
AsyncStorage.setItem('key_default', this.state.txtInputData)
this.setState({txtInputData: ''})
alert('Data salved!')
}else{
alert('Please, fill the data!')
}
}
getValue = () => {
AsyncStorage.getItem('key_default').then(value => this.setState({getValue: value}))
}
render() {
return (
<View style={styles.mainContainer}>
<Text>Using AsyncStorage</Text>
<TextInput
style={styles.textInput}
placeholder = 'type here'
value = {this.state.txtInputData}
onChangeText={data => this.setState({txtInputData: data})}
/>
<TouchableOpacity
onPress={this.saveValue}
style={styles.TouchableOpacity}
>
<Text>Save value</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.getValue}
style={styles.TouchableOpacity}
>
<Text> Read value</Text>
</TouchableOpacity>
<Text>Value read:{this.state.getValue}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
padding: 20,
},
textInput: {
marginTop: 10,
borderWidth: 1,
borderColor: '#fa19',
fontSize: 15
},
TouchableOpacity: {
width: '50%',
marginTop: 10,
borderWidth: 1,
borderColor: '#fa19',
padding: 10,
alignSelf: 'center',
alignItems: 'center'
}
})
Just setStat of txtInputData instead of getValue like this
getValue = () => {
AsyncStorage.getItem('key_default').then(value => this.setState({txtInputData: value}))
}

how to expand a component on click to full screen width and height with animation in reactnative

I have tried to implement the component expand to full screen in react native by using Layout animation in react-native but it was not good to look. Can any one help me in getting it?
changeLayout = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({ expanded: !this.state.expanded });
};
I expect to expand the component on click to full screen and again collapse it on click.
Set the initial value you want through the animation, obtain the screen width and height, and create a click function to execute.
This is an example that I made. Click this link if you want to run it yourself.
import React from 'react';
import { Animated, Text, View,Dimensions,Button } from 'react-native';
const screenwidth = Dimensions.get('screen').width
const screenheight = Dimensions.get('screen').height
class FadeInView extends React.Component {
state = {
fadeAnim: new Animated.Value(50),
fadeAnim2: new Animated.Value(50),
}
componentDidMount() {
}
animatebutton() {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: screenheight,
duration: 10000, // Make it take a while
}
).start();
Animated.timing( // Animate over time
this.state.fadeAnim2, // The animated value to drive
{
toValue: screenwidth,
duration: 10000, // Make it take a while
}
).start(); // Starts the animation
}
render() {
let { fadeAnim,fadeAnim2 } = this.state;
return (
<Animated.View // Special animatable View
style={{
...this.props.style,
height: fadeAnim,
width : fadeAnim2
}}
>
{this.props.children}
</Animated.View>
);
}
}
// You can then use your `FadeInView` in place of a `View` in your components:
export default class App extends React.Component {
constructor(props){
super(props);
this.state={
}
}
animatebutton(){
this.fade.animatebutton();
}
render() {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}} >
<FadeInView style={{backgroundColor: 'powderblue'}} ref={ani => this.fade = ani}>
<Text style={{fontSize: 28, textAlign: 'center', margin: 10}}>Fading in</Text>
</FadeInView>
<Button title="go animate" onPress={() => this.animatebutton()}/>
</View>
)
}
}
OR
You can use LayoutAnimation that you want to use. Look at my example.
import React, {Component} from "react";
import {
AppRegistry,
StyleSheet,
Text,
View,
TouchableOpacity,
LayoutAnimation,
} from 'react-native';
class App extends Component {
constructor() {
super();
this.state = {
check: false,
}
}
onPresscheck() {
// Uncomment to animate the next state change.
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
// Or use a Custom Layout Animation
// LayoutAnimation.configureNext(CustomLayoutAnimation);
this.setState({ check : !this.state.check});
}
render() {
var middleStyle = this.state.check === false ? {width: 20,height:20} : {width: "100%",height:"100%"};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={() => this.onPresscheck()}>
<Text>pressbutton</Text>
</TouchableOpacity>
<View style={[middleStyle, {backgroundColor: 'seagreen'}]}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
button: {
width:"100%",
height: 60,
backgroundColor: 'blue',
alignItems: 'center',
justifyContent: 'center',
margin: 8,
},
});
export default App;
Please refer to this blog :
https://dev-yakuza.github.io/en/react-native/react-native-animatable/
Also, try using this library. Use any animation type you want and render them.
Happy coding :)

React Native Animated lagging - Prevent Re-Rendering by function call

Context
Im want to get a countdown value which I then want to pass as prop to a separate component animating a progress bar. the data.json contains two type of elements:
text
question
the text will present the user an explanation. when the user pushes the button another text can be shown or a question. when it is a question the user will have certain amount of time for the answer. the progress bar will indicate the remaining time and shall only be shown when the current element is a question.
Problem
My problem is that the screen update for the current value is super slow and I can't see the reason. here is a simplified version of my code. the progress bar component will replace the <Text>{this.state.value}</Text> within the renderButtons function
Versions
I am running react native 0.60.
Update 1
I found out that when I trigger the countdown function by an onPress event, it works. but if I call it directly it doesn't. Why is that? How could I achieve it without the onPress
Update 2
the problem is that due to the animation the state value gets updated an by that a serenader gets triggered, which causes the function to be called each time, which is causing the lagging. this workaround seems to help but it feels ugly.
what would be a better approach to handle the re-rendering issue?
countdown = type => {
if (!this.state.countdownRunning) {
this.setState({ countdownRunning: true });
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(() => {
console.log('Animation DONE');
this.setState({ value: 100, percent: new Animated.Value(100) });
this.onPressNext();
this.setState({ countdownRunning: false });
}); // Start the animation
}
}
};
Lagging
import React, { Component } from 'react';
import { View, Text, StyleSheet, Animated, Easing } from 'react-native';
import { Button } from 'react-native-paper';
import Card from '../components/Card';
import data from '../data/data.json';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
},
surface: {
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
elevation: 12
}
});
class Home extends Component {
constructor(props) {
super(props);
this.countdown = this.countdown.bind(this);
this.state = {
i: 0,
questions: data,
value: 100,
percent: new Animated.Value(100)
};
}
onPressNext = () => {
const { i } = this.state;
if (i < 17) {
this.setState({ i: i + 1 });
} else {
this.setState({ i: 0 });
}
};
onPressBack = () => {
const { i } = this.state;
if (i > 0) {
this.setState({ i: i - 1 });
} else {
this.setState({ i: 17 });
}
};
countdown = type => {
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(); // Start the animation
}
};
renderButtons = type => {
if (type === 'question') {
this.countdown(type);
return (
<View>
<Text>{this.state.value}</Text>
</View>
);
}
return (
<View style={{ flexDirection: 'row' }}>
<Button mode="text" onPress={() => this.onPressBack()}>
Zurück
</Button>
<Button mode="contained" onPress={() => this.onPressNext(type)}>
Weiter
</Button>
</View>
);
};
render() {
const { i, questions } = this.state;
const { type, header, content } = questions.data[i.toString()];
return (
<View style={styles.container}>
<View style={{ flex: 2, justifyContent: 'flex-end' }}>
<Card>
<Text>{header}</Text>
<Text>{content}</Text>
</Card>
</View>
<View style={{ flex: 2 }}>{this.renderButtons(type)}</View>
</View>
);
}
}
export default Home;
No Lagging
import React, { Component } from 'react';
import { View, Text, StyleSheet, Animated, Easing } from 'react-native';
import { Button } from 'react-native-paper';
import Card from '../components/Card';
import data from '../data/data.json';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
},
surface: {
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
elevation: 12
}
});
class Home extends Component {
constructor(props) {
super(props);
this.countdown = this.countdown.bind(this);
this.state = {
i: 0,
questions: data,
value: 100,
percent: new Animated.Value(100)
};
}
onPressNext = () => {
const { i } = this.state;
if (i < 17) {
this.setState({ i: i + 1 });
} else {
this.setState({ i: 0 });
}
};
onPressBack = () => {
const { i } = this.state;
if (i > 0) {
this.setState({ i: i - 1 });
} else {
this.setState({ i: 17 });
}
};
countdown = type => {
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(); // Start the animation
}
};
renderButtons = type => {
if (type === 'question') {
return (
<View>
<Text>{this.state.value}</Text>
<Button mode="contained" onPress={() => this.countdown(type)}>
Weiter
</Button>
</View>
);
}
return (
<View style={{ flexDirection: 'row' }}>
<Button mode="text" onPress={() => this.onPressBack()}>
Zurück
</Button>
<Button mode="contained" onPress={() => this.onPressNext(type)}>
Weiter
</Button>
</View>
);
};
render() {
const { i, questions } = this.state;
const { type, header, content } = questions.data[i.toString()];
return (
<View style={styles.container}>
<View style={{ flex: 2, justifyContent: 'flex-end' }}>
<Card>
<Text>{header}</Text>
<Text>{content}</Text>
</Card>
</View>
<View style={{ flex: 2 }}>{this.renderButtons(type)}</View>
</View>
);
}
}
export default Home;

React-Native, How to add a View on the top of an alert or actionsheetIOS?

I want to add a View to cover my entire app.
details: Whenever AppState changes (to inactive or background), lock screen should appear to hide app contents and user should enter passcode to unlock it.
However, in IOS, dialogs from "Alert, ActionSheetIOS, Share" are on the top of everything.
As a result, my LockedView cannot cover these.
Is there a way to create cover these dialogs?
Below is the simple example code for reproducing my case.
(App.js)
import React, {Component} from "react"
import {
Alert,
AppState,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native"
export default class extends Component {
constructor (props) {
super(props)
AppState.addEventListener("change", this._handleAppStateChange)
this.state = {
isLocked: false
}
}
componentWillUnmount() {
this.unsubscribe()
AppState.removeEventListener("change", this._handleAppStateChange)
}
_handleAppStateChange = (nextAppState) => {
const isLocked = nextAppState !== "active"
this.setState({isLocked})
}
onPressAlert () {
Alert.alert("alert")
}
renderContent () {
return (
<View style={styles.content}>
<Text>UNLOCKED</Text>
<TouchableOpacity onPress={this.onPressAlert}>
<Text>Alert</Text>
</TouchableOpacity>
</View>
)
}
renderLockedView (isLocked) {
return isLocked ?
<View style={styles.locked}><Text>LOCKED</Text></View> : null
}
render() {
const {isLocked} = this.state
return (
<View style={styles.container}>
{this.renderContent()}
{this.renderLockedView(isLocked)}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
backgroundColor: "green",
justifyContent: "center",
alignItems: "center",
},
locked: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "red",
justifyContent: "center",
alignItems: "center",
},
})
With this code, app can cover contents(green) with lockedView(red) but alert.
I want to position lockedView on the top of alert.