React native updating the state using previous state behave differently - javascript

I have this simple react native app that when click on a button add a message to list and display the message for 2 second then the message get deleted from the list
import React, {
useEffect,
useRef,
useState,
} from 'react';
import {
Animated,
Button,
Text,
View,
} from 'react-native';
const getRandomMessage = () => {
const number = Math.trunc(Math.random() * 10000);
return 'Random message ' + number;
};
const Message = (props) => {
const opacity = useRef(new Animated.Value(0))
.current;
useEffect(() => {
Animated.sequence([
Animated.timing(opacity, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
Animated.delay(2000),
Animated.timing(opacity, {
toValue: 0,
duration: 500,
useNativeDriver: true,
}),
]).start(() => {
props.onHide();
});
}, []);
return (
<Animated.View
style={{
opacity,
transform: [
{
translateY: opacity.interpolate({
inputRange: [0, 1],
outputRange: [-20, 0],
}),
},
],
margin: 10,
marginBottom: 5,
backgroundColor: 'white',
padding: 10,
borderRadius: 4,
shadowColor: 'black',
shadowOffset: {
width: 0,
height: 3,
},
shadowOpacity: 0.15,
shadowRadius: 5,
elevation: 6,
}}
>
<Text>{props.message}</Text>
</Animated.View>
);
};
export default () => {
const [messages, setMessages] = useState([]);
return (
<>
<View
style={{
position: 'absolute',
top: 45,
left: 0,
right: 0,
}}
>
{messages.map((message) => (
<Message
key={message}
message={message}
onHide={() => {
setMessages((messages) =>
messages.filter(
(currentMessage) =>
currentMessage !== message
)
);
}}
/>
))}
</View>
<Button
title="Add message"
onPress={() => {
const message = getRandomMessage();
setMessages([...messages, message]);
}}
/>
</>
);
};
if i change the code that delete the message from the list
from
setMessages((messages) =>
messages.filter(
(currentMessage) =>
currentMessage !== message
)
to this
const temp = messages.filter(currentMessage => currentMessage !== message);
setMessages(temp);
the app behave differently and the whole screen re-render
is there any explanation for this because it took me hours to figure out what is the problem with my code but i do not understand why it did work when I changed the state update function to use the previous state

Related

How to reset value on click in React Native animated?

I have a progress bar screen inside navigation that I need to be reset every time user clicks on that specific route. Now it only plays animation when I go to that route for the first time. My question is: how to reset barWidth so that animation would play every time user clicks on specific route?
What have I tried?
I thought that the problem is that the component is not re-rendering and thats why value is not resetting, but it looks like the problem is that the width of the bar doesn't reset when user clicks on the screen after animation plays.
At first I've tried using useRef hook and later changed to simply setting the animated value to 0, but in both cases the bar's width didn't reset.
Code:
const { width } = Dimensions.get("screen");
const SettingsScreen = ({navigation}) => {
const isFocused = useIsFocused();
return (
<FlatList
contentContainerStyle={style.barContainer}
data={[1, 2, 3, 4, 5]}
keyExtractor={(_, index) => index.toString()}
renderItem={() => (
<ProgressBar isFocused={isFocused} navigation={navigation} />
)}
></FlatList>
);
};
const ProgressBar = ({ navigation, isFocused }) => {
//const barWidth = React.useRef(new Animated.Value(0)).current;
const barWidth = new Animated.Value(0);
console.log(barWidth);
const finalWidth = width / 2;
React.useEffect(() => {
const listener = navigation.addListener("focus", () => {
Animated.spring(barWidth, {
toValue: finalWidth,
bounciness: 10,
speed: 2,
useNativeDriver: false,
}).start();
});
return listener;
}, [navigation]);
return (
<View style={style.contentContainer}>
<Animated.View style={[style.progressBar, { width: barWidth }]} />
</View>
);
};
const style = StyleSheet.create({
contentContainer: {
flex: 1,
justifyContent: "center",
padding: 30,
},
barContainer: {
padding: 30,
},
progressBar: {
backgroundColor: "green",
width: width / 2,
height: 15,
borderRadius: 15,
},
});
The addListener function is Deprecated. try to use addEventListener instead.
also, why is it inside a const listener with the return listener?
as i see it you can write the useEffect like that:
React.useEffect(() => {
navigation.addEventListener("focus", () => {
Animated.spring(barWidth, {
toValue: finalWidth,
bounciness: 10,
speed: 2,
useNativeDriver: false,
}).start();
});
}, [navigation]);

ERROR Warning: Failed prop type: Invalid props.style key `elevation` supplied to `Image`

I am Creating a Tinder Clone in React Native and working on UI. But having some difficulty in showing images on top of the screen. I used zIndex but it didn't effect because it doesn't work on Android, I used elevation but it is giving me the error plus warning too. Here is the Code
import React, {useEffect, useState} from 'react';
import {
View,
StyleSheet,
useWindowDimensions,
Image,
Platform,
} from 'react-native';
import Card from './src/components/TinderCard';
import users from './assets/data/users';
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedGestureHandler,
useDerivedValue,
interpolate,
withSpring,
runOnJS,
} from 'react-native-reanimated';
import 'react-native-gesture-handler';
import {PanGestureHandler} from 'react-native-gesture-handler';
import like from './assets/images/LIKE.png';
import nope from './assets/images/nope.png';
const ROTATION = 60;
const SWIPE_VELOCITY = 800;
const App = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const [nexttIndex, setNextIndex] = useState(currentIndex + 1);
const currentProfile = users[currentIndex];
const nextProfile = users[nexttIndex];
const {width: screenWidth} = useWindowDimensions();
const hiddenTranslateX = 2 * screenWidth;
const translateX = useSharedValue(0);
const rotate = useDerivedValue(
() =>
interpolate(translateX.value, [0, hiddenTranslateX], [0, ROTATION]) +
'deg',
);
const cardStyle = useAnimatedStyle(() => ({
transform: [
{
translateX: translateX.value,
},
{rotate: rotate.value},
],
}));
const nextCardStyle = useAnimatedStyle(() => ({
transform: [
{
scale: interpolate(
translateX.value,
[-hiddenTranslateX, 0, hiddenTranslateX],
[1, 0.8, 1],
),
},
],
opacity: interpolate(
translateX.value,
[-hiddenTranslateX, 0, hiddenTranslateX],
[1, 0.5, 1],
),
}));
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, context) => {
context.startX = translateX.value;
},
onActive: (event, context) => {
translateX.value = context.startX + event.translationX;
},
onEnd: event => {
if (Math.abs(event.velocityX) < SWIPE_VELOCITY) {
translateX.value = withSpring(0);
return;
}
translateX.value = withSpring(
hiddenTranslateX * Math.sign(event.velocityX),
{},
() => runOnJS(setCurrentIndex)(currentIndex + 1),
);
},
});
useEffect(() => {
translateX.value = 0;
setNextIndex(currentIndex + 1);
}, [currentIndex, translateX]);
return (
<View style={styles.container}>
{nextProfile && (
<View style={styles.nextCardContainer}>
<Animated.View style={[styles.animatedCard, nextCardStyle]}>
<Card user={nextProfile} />
</Animated.View>
</View>
)}
{currentProfile && (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.animatedCard, cardStyle]}>
<Image source={like} style={styles.like} resizeMode="contain" />
<Image source={nope} style={styles.like} resizeMode="contain" />
<Card user={currentProfile} />
</Animated.View>
</PanGestureHandler>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
},
animatedCard: {
width: '90%',
height: '70%',
justifyContent: 'center',
alignItems: 'center',
},
nextCardContainer: {
width: '100%',
justifyContent: 'center',
alignItems: 'center',
...StyleSheet.absoluteFillObject,
},
like: {
width: 150,
height: 150,
position: 'absolute',
top: 10,
zIndex: 1,
elevation: 1,
},
});
export default App;
Help me to figure out what i am doing wrong here.
Have you tried using an ImageBackground instead? If you're trying to add box-shadow then and React native image background example

How to useState hooks with array

I am not able to push the number index in the array of useState.
Where I am going wrong, do I want to push the index of numbers when I click them?
I am extracting the previous state array and then I push new but nothing happens.
How to push a new element inside useState array React hook? My code doesn't work!!
Please someone check.
Game.js
import { StatusBar } from "expo-status-bar";
import React, { useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import RandomNumber from "./RandomNumber";
export default function Game(props) {
const [state, setstate] = useState([]);
let randomNumber = Array.from({ length: props.randomNumberCount }).map(
() => 1 + Math.floor(10 * Math.random())
);
let target = randomNumber
.slice(0, props.randomNumberCount - 2)
.reduce((acc, curr) => acc + curr, 0);
const isNumberSelected = (numberIndex) => {
return state.indexOf(numberIndex) >= 0;
};
const selectNumber = (numberIndex) => {
setstate((arr) => [...arr, numberIndex]);
};
return (
<View style={styles.container}>
<Text style={styles.header}>Target Sum Game</Text>
<Text style={styles.target}>{target}</Text>
<View style={styles.randomContainer}>
{randomNumber.map((randomNumber, index) => (
<RandomNumber
key={index}
id={index}
number={randomNumber}
isSelected={isNumberSelected(index)}
onClick={() => selectNumber}
/>
))}
</View>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#ddd",
paddingTop: 30,
},
target: {
fontSize: 30,
backgroundColor: "#aaa",
margin: 50,
marginHorizontal: 70,
textAlign: "center",
},
header: {
fontSize: 35,
backgroundColor: "dodgerblue",
textAlign: "center",
marginHorizontal: 30,
marginTop: 50,
},
randomContainer: {
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "space-around",
},
});
RandomNumber.js
import React from "react";
import { StyleSheet, Text, TouchableOpacity } from "react-native";
export default function RandomNumber(props) {
const handlePress = () => {
props.onClick(props.id);
};
return (
<TouchableOpacity onPress={handlePress()}>
<Text style={[styles.random, props.isSelected && styles.selected]}>
{props.number}
</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
random: {
backgroundColor: "#999",
width: 100,
marginHorizontal: 35,
marginVertical: 25,
fontSize: 35,
textAlign: "center",
},
selected: {
opacity: 0.3,
},
});
you are not calling the function
onClick={() => selectNumber(index)}
You need to change the onClick prop and pass the randomNumber (or index depending of what you want to do) to the selectNumber function:
// Not sure if you want to pass randonNumber or index but you get the idea
onClick={() => selectNumber(randomNumber)}
<TouchableOpacity onPress={handlePress()}>
should be
<TouchableOpacity onPress={()=>handlePress()}>
and
() => selectNumber
should be
() => selectNumber()
please try it
Might Be This Helpful:
Home.Js
import React, {useState, useEffect} from 'react';
import {StyleSheet, Text, View, TouchableOpacity} from 'react-native';
import RandomNumber from './RandomNumber';
const Home = props => {
const [state, setstate] = useState([]);
useEffect(() => {
console.log('state', state);
}, [state]);
let randomNumber = Array.from({length: 10}).map(
() => 1 + Math.floor(10 * Math.random()),
);
let target = randomNumber
.slice(0, props.randomNumberCount - 2)
.reduce((acc, curr) => acc + curr, 0);
const isNumberSelected = numberIndex => {
return state.indexOf(numberIndex) >= 0;
};
const selectNumber = numberIndex => {
console.log('numberIndex', numberIndex);
setstate(arr => [...arr, numberIndex]);
};
return (
<View style={styles.container}>
<Text style={styles.header}>Target Sum Game</Text>
<Text style={styles.target}>{target}</Text>
<View style={styles.randomContainer}>
{randomNumber.map((randomNumber, index) => {
return (
<RandomNumber
key={index}
id={index}
number={randomNumber}
isSelected={isNumberSelected(index)}
onClick={() => selectNumber(randomNumber)}
/>
);
})}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ddd',
paddingTop: 30,
},
target: {
fontSize: 30,
backgroundColor: '#aaa',
margin: 50,
marginHorizontal: 70,
textAlign: 'center',
},
header: {
fontSize: 35,
backgroundColor: 'dodgerblue',
textAlign: 'center',
marginHorizontal: 30,
marginTop: 50,
},
randomContainer: {},
});
export default Home;
RandomNumber.js
import React from 'react';
import {StyleSheet, Text, View, TouchableOpacity} from 'react-native';
export default function RandomNumber(props) {
const handlePress = () => {
props.onClick(props.id);
};
return (
<View style={{}}>
<TouchableOpacity onPress={() => handlePress()}>
<Text style={[styles.random, props.isSelected && styles.selected]}>
{props.number}
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
random: {
backgroundColor: '#999',
width: 100,
height: 100,
marginHorizontal: 35,
marginVertical: 25,
fontSize: 35,
textAlign: 'center',
},
selected: {
opacity: 0.3,
},
});
Output Log:

How to create a slide down and up notification indicator in react native?

How to create a slide down and up notification indicator in react native see below for and example
I would like the notification to slide down for about 3 seconds at the top of the screen and slide up back off the screen after 3 seconds
I tried this below but it is not working
const [stateAnimate, setAnimate] = useState(new Animated.Value(300));
const slideDown = () => {
Animated.spring(stateAnimate, {
toValue: 0,
}).start();
slideUp();
};
const slideUp = setTimeout(() => {
Animated.spring(stateAnimate, {
toValue: 0,
}).start();
clearTimeout(slideUp);
}, 3000);
<View>
<Animated.View style={[{ position: 'absolute', right: 0,left:0,backgroundColor: '#0400ff', height: '20%', width: '100%', }, { transform: [{ translateX: stateAnimate }] }]}>
<Text style={{ fontSize: 16, color: '#fff', alignSelf: 'center',marginTop:10 }}>loading please wait...</Text>
</Animated.View>
<View style={{ marginTop: 200 }}>
<Button
title="Slide"
onPress={() => {
slideDown();
}}
/>
</View>
</View>
I made a Toast like this in my app.
Toast.js:
import React from 'react';
import {Image, SafeAreaView, FlatList, TextInput, ScrollView, View, TouchableOpacity, Text} from 'react-native';
import * as Animatable from 'react-native-animatable';
import SafeArea, { type SafeAreaInsets } from 'react-native-safe-area'
export default class Toast extends React.Component {
singletonInstance = null;
state = {
visible: false,
title: "",
description: "",
backgroundColor: "",
textColor: ""
}
componentDidMount() {
Toast.singletonInstance = this;
this._isMounted = true;
SafeArea.getSafeAreaInsetsForRootView().then((result) => {
this.setState({safeInsetTop: result.safeAreaInsets.top});
});
}
componentWillUnmount() {
this._isMounted = false;
this.setState = (state,callback)=>{
return;
};
}
static show(title, description, backgroundColor, textColor) {
Toast.singletonInstance._openPanel(title, description, backgroundColor, textColor);
}
_openPanel(title, description, backgroundColor, textColor) {
if (this._isMounted) {
this.setState({
visible: true,
title: title,
description: description,
backgroundColor: backgroundColor,
textColor: textColor
});
this.setCloseTimer()
} else {
this._isMounted = true;
}
};
close = () => {
if (this._isMounted) {
if (this.view != null) {
this.view.fadeOutUp().then(endState =>
this.setState({visible: false})
);
} else {
this.setState({visible: false})
}
}
};
setCloseTimer() {
this.closeTimer = setTimeout(() => {
this.close()
}, 4000);
}
handleViewRef = ref => this.view = ref;
render() {
if (this.state.visible) {
return (
<View style={{width: '100%', position: 'absolute', top: 0, left: 0, right: 0}}>
<Animatable.View ref={this.handleViewRef} animation="slideInDown" style={{flex: 1, paddingTop: this.state.safeInsetTop, paddingHorizontal: 20, paddingBottom: 20, backgroundColor: this.state.backgroundColor}} >
<Text ellipsizeMode='tail' numberOfLines={1} style={{fontFamily: "Nunito-ExtraBold", fontSize: 12, width: '100%', marginLeft: 6, marginTop: 4, color: this.state.textColor}}>{this.state.title}</Text>
<Text ellipsizeMode='tail' style={{fontFamily: "Nunito-Bold", fontSize: 18, width: '100%', marginLeft: 6, marginTop: 4, color: this.state.textColor}}>{this.state.description}</Text>
</Animatable.View>
</View>
)
} else {
return null;
}
}
}
And then I use it in my other files like this:
...
import Toast from '../components/Toast.js'
...
...
Toast.show("Warning!", "You are about to delete everything", "white", "red");
...
I'm sure there is a prettier solution or a module somewhere, but it works for me :-)
You can use react-native-paper
Banner- React Native Paper
import React from 'react';
import { Banner } from 'react-native-paper';
const MyComponent = () => {
const [visible, setVisible] = React.useState(true);
return <Banner
visible={visible}
actions={[
{
label: 'Fix it',
onPress: () => setVisible(false),
},
{
label: 'Learn more',
onPress: () => setVisible(false),
},
]}
icon={({size}) => (
<Image
source={{
uri: 'https://avatars3.githubusercontent.com/u/17571969?s=400&v=4',
}}
style={{
width: size,
height: size,
}}
/>
)}>
There was a problem processing a transaction on your credit card.
</Banner>
}

React Native Make View "Hug" the Top of the Keyboard

Let's say I have a view that is positioned absolute at the bottom of the screen. This view contains a text input. When the text input is focused, I want the bottom of the view to touch the top of the keyboard.
I've been messing around with KeyboardAvoidingView, but the keyboard keeps going over my view. Is it not possible to make this work with position absolute?
What other method can I try? Thanks!
Few days ago I have the same problem (although I have a complex view with TextInput as a child) and wanted not only the TextInput to be focused but the whole view to be "attached" to the keyboard. What's finally is working for me is the following code:
constructor(props) {
super(props);
this.paddingInput = new Animated.Value(0);
}
componentWillMount() {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillHideSub.remove();
}
keyboardWillShow = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: 60,
}).start();
};
keyboardWillHide = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: 0,
}).start();
};
render() {
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
[...]
<Animated.View style={{ marginBottom: this.paddingInput }}>
<TextTranslateInput />
</Animated.View>
</KeyboardAvoidingView>
);
}
where [..] you have other views.
Custom hook:
import { useRef, useEffect } from 'react';
import { Animated, Keyboard, KeyboardEvent } from 'react-native';
export const useKeyboardHeight = () => {
const keyboardHeight = useRef(new Animated.Value(0)).current;
useEffect(() => {
const keyboardWillShow = (e: KeyboardEvent) => {
Animated.timing(keyboardHeight, {
duration: e.duration,
toValue: e.endCoordinates.height,
useNativeDriver: true,
}).start();
};
const keyboardWillHide = (e: KeyboardEvent) => {
Animated.timing(keyboardHeight, {
duration: e.duration,
toValue: 0,
useNativeDriver: true,
}).start();
};
const keyboardWillShowSub = Keyboard.addListener(
'keyboardWillShow',
keyboardWillShow
);
const keyboardWillHideSub = Keyboard.addListener(
'keyboardWillHide',
keyboardWillHide
);
return () => {
keyboardWillHideSub.remove();
keyboardWillShowSub.remove();
};
}, [keyboardHeight]);
return keyboardHeight;
};
#jazzdle example works great! Thank you for that!
Just one addition - in keyboardWillShow method, one can add event.endCoordinates.height so paddingBottom is exact height as keyboard.
keyboardWillShow = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: event.endCoordinates.height,
}).start();
}
Using Functional Component. This works for both iOS and Android
useEffect(() => {
const keyboardVisibleListener = Keyboard.addListener(
Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
handleKeyboardVisible
);
const keyboardHiddenListener = Keyboard.addListener(
Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
handleKeyboardHidden
);
return () => {
keyboardHiddenListener.remove();
keyboardVisibleListener.remove();
};}, []);
const handleKeyboardVisible = (event) => {
Animated.timing(paddingInput, {
duration: event.duration,
toValue: 60,
useNativeDriver: false,
});};
const handleKeyboardHidden = (event: any) => {
Animated.timing(paddingInput, {
duration: event.duration,
toValue: 0,
useNativeDriver: false,
});};
React Native now supports an InputAccessoryView which can be used for exactly this purpose - even for anchored TextInputs.
Here's a specific example: https://github.com/facebook/react-native/blob/main/packages/rn-tester/js/examples/InputAccessoryView/InputAccessoryViewExample.js
You can use flexbox to bottom position the element. Here's simple example -
render() {
return (
<View style={styles.container}>
<View style={styles.top}/>
<View style={styles.bottom}>
<View style={styles.input}>
<TextInput/>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
top: {
flex: .8,
},
bottom: {
flex: .2,
},
input: {
width: 200,
},
});

Categories