How to create countdown component with React-Native and Moment.js? - reactjs

I am creating simple countdown component with React-Native, for date formatting I am using moment.js, I am getting data from API call in seconds format, then providing to child component, I want to transform this kind of data 121 into 2:01, How to use moment to achieve this result and do not kill performance?
import { MaterialCommunityIcons } from "#expo/vector-icons";
import moment from "moment";
import React, { useEffect, useState} from "react";
import { View, Text} from "react-native";
const Timer: React.FC<{ expirationDate: number,focus:boolean, loading:boolean}> = ({ expirationDate, focus, loading}) => {
const [inactive, setIncative] = useState(false);
const [timerCount, setTimer] = useState<number>(0) //can be any number provided from props
useEffect(() => {
setTimer((prev:number)=> (expirationDate))
let interval = setInterval(() => {
setTimer(lastTimerCount => {
lastTimerCount <= 1 && clearInterval(interval)
return lastTimerCount - 1
})
}, 1000)
return () => clearInterval(interval)
}, [loading]);
return (
<View
style={{
backgroundColor: expirationDate < 300 || inactive ? "#edbcbc" : "#cee5f4",
width: 70,
borderRadius: 7,
paddingVertical: 6,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
>
<View style={{ position: "relative", left: 4 }}>
<MaterialCommunityIcons name="timer-outline" size={13} color={expirationDate < 300 || inactive ? "#f54c4c" : "#004978"} />
</View>
//line sholud be changed
<Text>{moment.duration(timerCount).format("h:mm")}</Text>
</View>
);
};
export default Timer;

I found solotion: using moment utc method and transform state value into miliseconds
import { MaterialCommunityIcons } from "#expo/vector-icons";
import moment from "moment";
import React, { useEffect, useState} from "react";
import { View, Text} from "react-native";
const Timer: React.FC<{ expirationDate: number,focus:boolean, loading:boolean}> = ({ expirationDate, focus, loading}) => {
const [inactive, setIncative] = useState(false);
const [timerCount, setTimer] = useState<number>(0)
useEffect(() => {
setTimer((prev:number)=> (expirationDate))
let interval = setInterval(() => {
setTimer(lastTimerCount => {
lastTimerCount <= 1 && clearInterval(interval)
return lastTimerCount - 1
})
}, 1000)
return () => clearInterval(interval)
}, [loading]);
return (
<View
style={{
backgroundColor: expirationDate < 300 || inactive ? "#edbcbc" : "#cee5f4",
width: 70,
borderRadius: 7,
paddingVertical: 6,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
>
<View style={{ position: "relative", left: 4 }}>
<MaterialCommunityIcons name="timer-outline" size={13} color={expirationDate < 300 || inactive ? "#f54c4c" : "#004978"} />
</View>
<Text>{moment.utc(timerCount * 1000).format("mm:ss")}</Text>
</View>
);
};
export default Timer;

Related

React Native Gesture Handler + Reanimated Flat List Scroll

I'm having a problem with react-native-gesture handler animation in a FlatList.
when i try to scroll on the flatList the behavior of PanGesture not trigger scroll event.
Reanimated Version - ~2.5
Flatlist
<FlatList
data={test}
ref={flatListRef}
ItemSeparatorComponent={Divider}
style={{
marginTop: 18,
}}
scrollEnabled
renderItem={({ item }) => {
return (
<ActionItem
simultaneousHandlers={flatListRef}
actions={[
{
type: 'UPDATE',
handlePress: () => {
console.log(item.id);
},
},
{
type: 'UPDATE',
handlePress: () => {
console.log(item.id);
},
},
]}
>
<View style={styles.container}>
<Text style={{}}>{item.name}</Text>
</View>
</ActionItem>
);
}}
/>
ActionItem
import { useTheme } from '#hooks';
import React from 'react';
import {
StyleSheet,
View,
Text,
Dimensions,
TouchableOpacity,
} from 'react-native';
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
withSpring,
} from 'react-native-reanimated';
import { IActionItem } from './types';
import Edit from '#assets/svgs/icons/edit.svg';
const threshold = (-Dimensions.get('window').width - 18) * 0.05;
function ActionItem<T>({
children,
actions,
simultaneousHandlers,
}: IActionItem<T>) {
const [actionsWidth, setActionsWidth] = React.useState<number>(0);
const translateX = useSharedValue(0);
const theme = useTheme();
const gestures = useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onStart: () => {
translateX.value = withTiming(0);
},
onActive: (e) => {
if (e.translationX < 0) {
translateX.value = withSpring(e.translationX);
}
},
onEnd: (e) => {
const shouldHideActions = e.translationX > threshold;
if (shouldHideActions) {
translateX.value = withTiming(0);
} else {
translateX.value = withSpring(-actionsWidth);
}
},
});
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
],
};
});
return (
<Animated.View style={styles.container}>
<Animated.View
style={styles.actionsContainer}
onLayout={(e) => {
setActionsWidth(e.nativeEvent.layout.width);
}}
>
{actions?.map((action, index) => {
return (
<TouchableOpacity
key={index}
onPress={action.handlePress}
style={[
{
marginRight: index !== actions.length - 1 ? 20 : 0,
marginLeft: index === 0 ? 20 : 0,
},
]}
>
{action.type === 'UPDATE' && <Edit />}
</TouchableOpacity>
);
})}
</Animated.View>
<PanGestureHandler
simultaneousHandlers={simultaneousHandlers}
onGestureEvent={gestures}
>
<Animated.View style={[animatedStyles, styles.action]}>
{children}
</Animated.View>
</PanGestureHandler>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
elevation: 10,
},
actionsContainer: {
position: 'absolute',
flexDirection: 'row',
alignItems: 'center',
height: '100%',
flex: 1,
right: 0,
},
action: {
backgroundColor: 'red',
},
});
export default ActionItem;
i'm using the flat list from React-Native-Gesture-handler and i'm passing the reference of flatlist to scroll work but doesn't work, anyone have some ideia why ?

How to display a value back in parent component in react native?

I have a page which shows list of coins.
From there I navigated to another component and passing the coin name as param.
From the second page, I have filtered all the orders based on the coin name received in params.Here, I calculated the average value. How to pass this back to parent page so that I can see the average value of orders beside each coin?
//screen1 code:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
export default function CoinCreate(props) {
const [coinText, setCoinText] = useState("");
const [coins, setCoins] = useState([]);
const coinRef = firebase.firestore().collection("coins");
const userID = props.extraData.id;
useEffect(() => {
coinRef
.where("authorID", "==", userID)
.orderBy("createdAt", "desc")
.onSnapshot(
(querySnapshot) => {
const newCoins = [];
querySnapshot.forEach((doc) => {
const coin = doc.data();
coin.id = doc.id;
newCoins.push(coin);
});
setCoins(newCoins);
},
(error) => {
console.log(error);
}
);
}, []);
const onAddButtonPress = () => {
if (coinText && coinText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: coinText,
authorID: userID,
createdAt: timestamp,
};
coinRef
.add(data)
.then((_doc) => {
setCoinText("");
Keyboard.dismiss();
})
.catch((error) => {
alert(error);
});
}
};
const renderCoin = ({ item, index }) => {
return (
<View style={styles1.rowFront}>
<TouchableWithoutFeedback
onPress={() =>
props.navigation.navigate("Orders", {
coin: item.text,
userID: userID,
})
}
>
<Text>
{index}. {item.text}
</Text>
</TouchableWithoutFeedback>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new coin"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setCoinText(text)}
value={coinText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{coins && (
<SwipeListView
data={coins}
keyExtractor={(item) => item.id}
renderItem={renderCoin}
removeClippedSubviews={true}
/>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
//screen2 code:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
} from "react-native";
import { Avatar, Button, Card, Title, Paragraph } from "react-native-paper";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { Icon } from "react-native-elements";
import { createIconSetFromFontello } from "#expo/vector-icons";
export default function OrderList(props) {
const LeftContent = (props) => <Avatar.Icon {...props} icon="folder" />;
const [orderText, setOrderText] = useState("");
const [orders, setOrders] = useState([]);
const orderRef = firebase.firestore().collection("orders");
const userID = props.route.params.userID;
const coin = props.route.params.coin;
//averageValue = (totalCost / totalCount).toString();
const [averageValue, setAverageValue] = useState("");
useEffect(() => {
orderRef
.where("authorID", "==", userID)
.where("name", "==", coin)
.orderBy("createdAt")
.onSnapshot(
(querySnapshot) => {
const newOrders = [];
querySnapshot.forEach((doc) => {
const order = doc.data();
order.id = doc.id;
newOrders.push(order);
});
setOrders(newOrders);
},
(error) => {
console.log(error);
}
);
}, []);
useEffect(() => {
//calculate and set anything like totalCost, averageValue, etc here
console.log("---came to orders effect---");
//console.log(orders);
let totalCost = 0;
let totalCount = 0;
orders.forEach((item, index) => {
console.log(item);
console.log(index);
totalCost += parseFloat(item.amount);
totalCount += parseFloat(item.count);
});
setAverageValue((totalCost / totalCount).toString());
}, [orders]);
/*
useEffect(() => {
let avg = (parseFloat(totalCost) / parseFloat(totalCount)).toString();
console.log("Avg:" + avg);
setAverageValue(avg);
}, [totalCount, totalCost]);
*/
const onAddButtonPress = () => {
props.navigation.navigate("CreateOrder", {
coin: coin,
userID: userID,
orderRef,
});
};
const renderOrder = ({ item, index }) => {
//console.log("----------------------");
//console.log(item.createdAt.toDate().toString());
//console.log("----------------------");
//setTotalCost(parseFloat(totalCost) + parseFloat(item.price));
//setTotalCount(parseFloat(totalCount) + parseFloat(item.count));
//totalCost = parseFloat(totalCost) + parseFloat(item.price);
//totalCount = parseFloat(totalCount) + parseFloat(item.count);
//console.log(totalCost);
//console.log(totalCount);
return (
<View style={styles1.rowFront}>
<Text>
{index}. {item.price} {item.amount} {item.count}
{"\n" + item.createdAt.toDate().toString()}
</Text>
<Icon name={"flight-takeoff"} />
</View>
);
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Click here to create new order..</Text>
</TouchableOpacity>
{orders.length === 0 && (
<Text>Please order some coins in currency: {coin}</Text>
)}
{orders && orders.length > 0 && (
<>
<Text>Average Value: {averageValue}</Text>
<SwipeListView
data={orders}
keyExtractor={(item) => item.id}
renderItem={renderOrder}
removeClippedSubviews={true}
/>
</>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
To simplify the process, I created a normal function(not react component) with orderListData(coin,userID) in a separate file and imported that and calling that function from the screen 1, but getting error as
TypeError: (0, _OrderListData.orderListData) is not a function. (In '(0, _OrderListData.orderListData)("ada", "nMOpBupcptZF8YlxiJYFX7vPMtC2")', '(0, _OrderListData.orderListData)' is undefined)
My code:
OrderListData.js
import { firebase } from "../../firebase/config";
export default function orderListData(coin, userID) {
let orders = [];
const orderRef = firebase.firestore().collection("orders");
//const userID = props.route.params.userID;
//const coin = props.route.params.coin;
let totalCost = 0;
let totalCount = 0;
let averagePrice = 0;
orderRef
.where("authorID", "==", userID)
.where("name", "==", coin)
.orderBy("createdAt")
.onSnapshot(
(querySnapshot) => {
querySnapshot.forEach((doc) => {
const order = doc.data();
order.id = doc.id;
orders.push(order);
});
},
(error) => {
console.log(error);
}
);
orders.forEach((item, index) => {
totalCost += parseFloat(item.amount);
totalCount += parseFloat(item.count);
});
averagePrice = totalCost / totalCount;
return {
averagePrice,
totalCost,
totalCount,
};
}
And the modified screen 1:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { orderListData } from "./OrderListData";
export default function CoinCreate(props) {
console.log("testing");
console.log(orderListData("ada", "nMOpBupcptZF8YlxiJYFX7vPMtC2"));
const [coinText, setCoinText] = useState("");
const [coins, setCoins] = useState([]);
const coinRef = firebase.firestore().collection("coins");
const userID = props.extraData.id;
useEffect(() => {
coinRef
.where("authorID", "==", userID)
.orderBy("createdAt", "desc")
.onSnapshot(
(querySnapshot) => {
const newCoins = [];
querySnapshot.forEach((doc) => {
const coin = doc.data();
coin.id = doc.id;
newCoins.push(coin);
});
setCoins(newCoins);
},
(error) => {
console.log(error);
}
);
}, []);
const onAddButtonPress = () => {
if (coinText && coinText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: coinText,
authorID: userID,
createdAt: timestamp,
};
coinRef
.add(data)
.then((_doc) => {
setCoinText("");
Keyboard.dismiss();
})
.catch((error) => {
alert(error);
});
}
};
const renderCoin = ({ item, index }) => {
let { averagePrice, totalCost, totalCount } = orderListData(
item.text,
userID
);
return (
<View style={styles1.rowFront}>
<TouchableWithoutFeedback
onPress={() =>
props.navigation.navigate("Orders", {
coin: item.text,
userID: userID,
})
}
>
<Text>
{index}. {item.text} {averagePrice}
</Text>
</TouchableWithoutFeedback>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new coin"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setCoinText(text)}
value={coinText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{coins && (
<SwipeListView
data={coins}
keyExtractor={(item) => item.id}
renderItem={renderCoin}
removeClippedSubviews={true}
/>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
Any suggestion on this please.
There are different ways to solve this problem,
Your requirement is to update one screen based on another screen, so you can think of using something state management for this but in your case you have the trigger of going back so use the functions of the navigation library to do that.
So I'll put a working example here using a simple scenario of sending an array
Let say that you have a home screen, from here we go to a CreateOrder screen and it will send data do this screen via navigation.navigate (navigate function will navigate to an existing screen or push a new screen so here you will be sent back).
function HomeScreen({ navigation, route }) {
React.useEffect(() => {
if (route.params?.orders) {
}
}, [route.params?.orders]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Create post"
onPress={() => navigation.navigate('CreateOrder')}
/>
<Text style={{ margin: 10 }}>Post: {route.params?.orders?.length}</Text>
</View>
);
}
You can use the useEffect to update if the param is updated and use the route prop.
The second screen would override the back button and also have a button to navigate back. Here we send the orders array via navigation.navigate
function CreateOrdersScreen({ navigation, route }) {
const [orders, setOrders] = React.useState('');
React.useLayoutEffect(() => {
navigation.setOptions({
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
navigation.navigate('Home', { orders });
}}
/>
),
});
}, [navigation, orders]);
return (
<>
<Text>{orders.length}</Text>
<Button
title="Add"
onPress={() => {
setOrders([...orders, 1]);
}}
/>
<Button
title="Done"
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { orders },
merge: true,
});
}}
/>
</>
);
}
You can see a running example here
https://snack.expo.io/#guruparan/5b84d0
References
https://reactnavigation.org/docs/params/#passing-params-to-a-previous-screen
https://reactnavigation.org/docs/header-buttons/#overriding-the-back-button

React Native Hooks Search Filter FlatList

I am trying to make a Covid19 React Native Expo app. It contains a search filter from which user will select a country then the selected country results will be shown to the user. I keep getting this error on my Android device "Unexpected Identifier You" while on web pack the countries load but they don't filter correctly.
Working Snack Link: https://snack.expo.io/#moeez71/ac5758
Here is my code:
import React, { useState, useEffect } from "react";
import {
ActivityIndicator,
Alert,
FlatList,
Text,
StyleSheet,
View,
TextInput,
} from "react-native";
export default function ABCDEE() {
const [arrayholder, setArrayholder] = useState([]);
const [text, setText] = useState("");
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const fetchAPI = () => {
return fetch("https://api.covid19api.com/countries")
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson);
setLoading(false);
setArrayholder(responseJson);
})
.catch((error) => {
console.error(error);
});
};
useEffect(() => {
fetchAPI();
});
const searchData = (text) => {
const newData = arrayholder.filter((item) => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setData(newData);
setText(text);
};
const itemSeparator = () => {
return (
<View
style={{
height: 0.5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
};
return (
<View>
{loading === false ? (
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={(text) => searchData(text)}
value={text}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={data}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={itemSeparator}
renderItem={({ item }) => (
<Text style={styles.row}>{item.Country}</Text>
)}
style={{ marginTop: 10 }}
/>
</View>
) : (
<Text>loading</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: "center",
flex: 1,
margin: 5,
},
row: {
fontSize: 18,
padding: 12,
},
textInput: {
textAlign: "center",
height: 42,
borderWidth: 1,
borderColor: "#009688",
borderRadius: 8,
backgroundColor: "#FFFF",
},
});
You had made two mistakes in the above code
useEffect second parameter should be a empty array for it act as componentDidMount()
useEffect(() => {
fetchAPI();
},[])
in FlatList renderItem need to destructure the item.
renderItem={( {item} ) => <Text style={styles.row}
>{item.Country}</Text>}
Working code
import React, {useState, useEffect} from "react"
import { ActivityIndicator, Alert, FlatList, Text, StyleSheet, View, TextInput } from 'react-native';
export default function ABCDEE(){
const [arrayholder,setArrayholder] =useState([])
const[text, setText] = useState('')
const[data, setData] = useState([])
const [loading , setLoading] = useState(true)
const fetchAPI = ()=> {
return fetch('https://api.covid19api.com/countries')
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson)
setLoading(false)
setArrayholder(responseJson)
}
)
.catch((error) => {
console.error(error);
});
}
useEffect(() => {
fetchAPI();
},[])
const searchData= (text)=> {
const newData = arrayholder.filter(item => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1
});
setData(newData)
setText(text)
}
const itemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
return (
<View style={{flex:1}} >
{loading === false ?
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={(text) => searchData(text)}
value={text}
underlineColorAndroid='transparent'
placeholder="Search Here" />
<FlatList
data={data}
keyExtractor={ (item, index) => index.toString() }
ItemSeparatorComponent={itemSeparator}
renderItem={( {item} ) => <Text style={styles.row}
>{item.Country}</Text>}
style={{ marginTop: 10 }} />
</View>
: <Text>loading</Text>}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
flex: 1,
margin: 5,
},
row: {
fontSize: 18,
padding: 12
},
textInput: {
textAlign: 'center',
height: 42,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 8,
backgroundColor: "#FFFF"
}
});
There are multiple issues with your implementation. I will point out some mistake/ignorance. You can clean up you code accordingly.
Do not create 2 state to keep same data. ie. arrayholder and data.
Change text value on search, don't the data. based on that text filter
Hooks always define some variable to be watched.
Update: Seems there is an issue with flex in android view, i use fixed height it is visible.
Just a hack for android issue. minHeight
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
flex: 1,
margin: 5,
minHeight: 800,
},
Working link: https://snack.expo.io/kTuT3uql_
Updated code:
import React, { useState, useEffect } from 'react';
import {
ActivityIndicator,
Alert,
FlatList,
Text,
StyleSheet,
View,
TextInput,
} from 'react-native';
export default function ABCDEE() {
const [text, setText] = useState('');
const [state, setState] = useState({ data: [], loading: false }); // only one data source
const { data, loading } = state;
const fetchAPI = () => {
//setState({data:[], loading: true});
return fetch('https://api.covid19api.com/countries')
.then(response => response.json())
.then(data => {
console.log(data);
setState({ data, loading: false }); // set only data
})
.catch(error => {
console.error(error);
});
};
useEffect(() => {
fetchAPI();
}, []); // use `[]` to avoid multiple side effect
const filterdData = text // based on text, filter data and use filtered data
? data.filter(item => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
})
: data; // on on text, u can return all data
console.log(data);
const itemSeparator = () => {
return (
<View
style={{
height: 0.5,
width: '100%',
backgroundColor: '#000',
}}
/>
);
};
return (
<View>
{loading === false ? (
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={text => setText(text)}
value={text}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={filterdData}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={itemSeparator}
renderItem={({ item }) => (
<Text style={styles.row}>{item.Country}</Text>
)}
style={{ marginTop: 10 }}
/>
</View>
) : (
<Text>loading</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
//flex: 1,
margin: 5,
height: 800,
},
row: {
fontSize: 18,
padding: 12,
},
textInput: {
textAlign: 'center',
height: 42,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 8,
backgroundColor: '#333',
},
});
It seems like the error comes from the catch block when you fetch your data. The response comes with a 200 status, so it's not an issue with the endpoint itself. I console.logged the response and it seems fine. The problem is when you parse the response and try to use it in the second then() block, so the catch block fires up. I could not debug it right in the editor you use, but I would check what type of object I'm receiving from the API call.
This is not a direct answer to your question but I didn't want this to get lost in the comments as it is directly related with your efforts on this app.
App Store and Google Play Store no longer accept apps that have references to Covid-19 https://developer.apple.com/news/?id=03142020a
You can only publish apps about Covid-19 if you are reputable source like a government's health department.
Therefore, I urge you to reevaluate your efforts on this app.

State not changing in react native

I'm pretty new to react native and having some trouble with setting a new state. Everything works fine until the third textinput shows up, when i start writing inside it the counter is stuck at 1, when it should be at 2, and furthermore update the textInputList too two TextInput elements. I want to understand why the counter does not change, and how to solve this issue:)
import React from 'react';
import { useState } from 'react';
import { Button, View, StyleSheet, TextInput } from 'react-native';
import colour from '../constants/Colors';
import StartButton from '../components/Buttons/BackToBackButton';
function ShowNames(props) {
return (
<View style={styles.lineContainer}>
<TextInput
style = {{ width: '70%', height: 40, borderColor: 'white', borderWidth: 2 }}
placeholder='Legg in navn her'
placeholderTextColor='white'
selectionColor='black'
onChangeText={props.handleTextChange}
/>
</View>
)
}
export default function BackToBack(props) {
const [nameList, setList] = useState([]);
const [counter, setCounter] = useState(0);
const [textInputList, setInputList] = useState(null)
const handleTextChange = (text, id) => {
tempList = nameList
tempList[id] = text
setList(tempList)
if (id == counter) {
setCounter(counter + 1)
AddNameInputs()
}
}
function AddNameInputs()
var tempList = [];
for (let i = 0; i < counter; i++) {
console.log('i: ' + i)
tempList.push(
<View style={styles.lineContainer} key={i+2}>
<TextInput
style={{ width: '70%', height: 40, borderColor: 'white', borderWidth: 2 }}
placeholder='Legg in navn her'
placeholderTextColor='white'
selectionColor='black'
onChangeText={(text) => handleTextChange(text, i+2)}
/>
</View>
)
}
setInputList(tempList)
}
return (
<View style={styles.container}>
<ShowNames handleTextChange={(text) => handleTextChange(text, 0)} />
<ShowNames handleTextChange={(text) => handleTextChange(text, 1)} />
{textInputList}
<StartButton title={"Start!"} height={100} />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colour.lightTurquoise,
alignItems: 'center',
justifyContent: 'flex-start',
paddingTop: 20,
paddingBottom: 20
// borderWidth: 4,
// borderColor: 'yellow'
},
lineContainer: {
flexDirection: 'row',
paddingBottom: 20
}
});
I think the problem is this line tempList = nameList. It would assume you are referencing the same object when you're setting state and not trigger the relevant updates. So the quickest fix would be to just clone it with the spread operator like tempList = [...nameList] ?

Set initial region after getting current location isn't working

I am using functional component with Hooks and I am getting user current location and assign it to MapView initialRegion but component doesn't rerender.
I tried to use reference ref with MapView component to call animateToRegion() but it's not working.
Is there is any thing that I miss?
here is my code sample:
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, Image, Platform, ScrollView, ActivityIndicator } from 'react-native';
import { DateMenu, Dropdown, DropdownTextInput, InputField} from '../../../GlobalReusableComponents/TextFields';
import { Space } from '../../../GlobalReusableComponents/Separators';
import { Button } from 'react-native-elements';
import axios from 'axios';
import MapView, { Marker } from 'react-native-maps';
import useReverseGeocoding from '../../../context/useReverseGeocoding';
import useCurrentLocation from '../../../context/useCurrentLocation';
import { getLocationAsync } from '../../../util/getCurrentLocation';
import { textStyles, buttonStyles } from '../../../globalStyles/styles';
import EStyleSheet from 'react-native-extended-stylesheet';
const latitudeDelta = 0.025
const longitudeDelta = 0.025
const JobLocationScreen = (props) => {
const [region, setRegion] = useState({
latitude: 38.907192,
longitude: -30.036871,
latitudeDelta,
longitudeDelta
});
const [latitude, setLatitude] = useState(null);
const [longitude, setLongitude] = useState(null);
const [reverseGeocodingApi, reverseGeocodingdata, isReverseGeacodingDone, reverseGeocodingErrorMessage] = useReverseGeocoding();
let mapRef = useRef(null);
useEffect(() => {
getYourCurrentLocation();
},
[])
useEffect(() => {
animateToRegion();
},
[region])
const onMapReady = () => {
if(!isMapReady) {
setIsMapReady(true);
}
};
const getYourCurrentLocation = async () => {
const { location } = await getLocationAsync();
console.log(location);
setRegion(region);
}
const onRegionChangeComplete = (selectedRegion) => {
setRegion(selectedRegion);
reverseGeocodingApi(selectedRegion);
}
const animateToRegion = () => {
mapRef.animateToRegion(region, 1000);
}
const onNextButtonPress = () => {
props.navigation.state.params.setSelectedValue(jobTags);
props.navigation.pop();
}
const _renderMapWithFixedMarker = () => {
return (
<View style={{flex: 1}}>
<MapView
ref={ref => {
mapRef = ref
}}
onMapReady={onMapReady}
style={styles.map}
initialRegion={region}
onRegionChangeComplete={(selectedRegion) => onRegionChangeComplete(selectedRegion)}
/>
<View style={styles.pinBadge}>
<Text
style={{color: EStyleSheet.value('$primaryDarkGray')}}
>
Move to choose Location
</Text>
</View>
<View style={styles.markerFixed}>
<Image style={styles.marker} source={require('../../../assets/pin.png')} />
</View>
</View>
);
}
return (
<View style={styles.container}>
<View
pointerEvents='none'
style={styles.inputFieldContainer}
>
<InputField
maxLength={35}
placeholder='Selected Address'
value={isReverseGeacodingDone? reverseGeocodingdata.results[0].formatted_address : 'Loading ...'}
/>
</View>
{_renderMapWithFixedMarker()}
<View style={styles.bodyContainer}>
<View style={styles.buttonContainer}>
<Button
title="Confirm Your Location"
buttonStyle={buttonStyles.button}
onPress={() => onNextButtonPress()}
/>
</View>
</View>
</View>
);
}
JobLocationScreen.navigationOptions = ({navigation}) => {
return {
title: 'Select your Location'
};
};
export default JobLocationScreen;
const styles = EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor'
},
inputFieldContainer: {
backgroundColor: '#f8f9f9',
paddingVertical: 20,
paddingHorizontal: 20
},
map: {
flex: 1
},
pinBadge: {
position: 'absolute',
paddingVertical: 10,
paddingHorizontal: 15,
top: '38%',
alignSelf: 'center',
borderRadius: 25,
backgroundColor: '#ffffff',
shadowColor: '#acaeb4',
shadowOffset: {
width: 0,
height: 3,
},
shadowOpacity: 0.5,
shadowRadius: 5,
elevation: 5
},
markerFixed: {
left: '50%',
marginLeft: -10,
marginTop: -6,
position: 'absolute',
top: '50%'
},
marker: {
width: 20,
height: 41
},
bodyContainer: {
marginHorizontal: 20
},
buttonContainer: {
position: 'absolute',
bottom: 20,
width: '100%'
}
})
You need to access the ref using refname.current to get access to the value.
<MapView ref={mapRef}
and then when want to access it, use .current:
const animateToRegion = () => {
mapRef.current.animateToRegion(region, 1000);
}
See the docs
Conceptual Demo
for react-native-maps-super-cluster
<MapView ref={mapRef}
const animateToRegion = () => {
mapRef.current.getMapRef().animateToRegion(region, 1000);
}

Resources