Fade out loading screen in React Native - reactjs

I've made a test weather app which has a loading screen with a timeout so it shows the Home screen after 6 sec (this will be replaced by the time the API-fetch is loading). Everything works fine however I'd like the loading screen to fade into the Home screen (now it just snaps right into it). Is there a way of doing this?
Thanks
export default function Home() {
const [isLoading, setIsLoading] = useState(true)
const [temperature, setTemperature] = useState(0)
const [weatherCondition, setWeatherCondition] = useState(null)
const lat = 60
const lon = 24
useEffect(() => {
setTimeout(() => {
fetch(`http://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&APPID=${API_KEY}&units=metric`)
.then(res => res.json())
.then(json => {
setTemperature(json.main.temp)
setWeatherCondition(json.weather[0].main)
setIsLoading(false)
})
.catch((e) => console.log(e))
}, 6000);
},[]);
return (
<View style={styles.container}>
{isLoading ?
<LoadingScreen/>
:
<Weather weather={weatherCondition} temperature={temperature}/>
}
</View>
)
}

You can use react-native-modal for your loading screen and in animationOut pass this value 'fadeOut'

You can do something like this.
First create a Spinner Component so you can use it wherever you want.
Spinner.js
It take on prop visible to show or hide the ActivityIndicator
import React from "react";
import {
View,
StyleSheet,
ActivityIndicator,
Modal,
Dimensions,
} from "react-native";
// dimenstion
const { width, height } = Dimensions.get("window");
const Spinner = ({ visible, ...props }) => {
if (!visible) return null;
return (
<Modal visible={visible} transparent animationType="fade">
<View style={styles.container}>
<ActivityIndicator color="white" size={height * 0.07} />
</View>
</Modal>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "rgba(1,0,23,0.65)",
alignItems: "center",
justifyContent: "center",
},
});
export default Spinner;
Now simply use it in your screen
First import it.
import Spinner from '.....';
and Use it like this.
return (
<View style={styles.container}>
<Spinner visible={isLoading} />
<Weather weather={weatherCondition} temperature={temperature} />
</View>
);

Related

React native page keeps crashing, I can't see why

I'm making a react native app on Expo, but the whole app crashes everytime I try to get into this one page. This is my first time using react native, and I'm still a beginner to react, but I can't see what's causing the issue. The error I'm getting is Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it., but I can't see where
import { StyleSheet, Text, View, TouchableOpacity, ScrollView, TextInput } from 'react-native'
import React, { useState, useEffect } from 'react'
// import CartCard from '../Components/CartCard'
import ItemCard from '../Components/ItemCard'
const Cart = ({navigation}) => {
const [details, setDetails] = useState([])
useEffect(()=>{
getData()
}, [])
const getData = async() => {
try {
const getValues = await AsyncStorage.getItem('object')
if(getValues !== null){
console.log(JSON.parse(getValues))
setDetails(getValues)
// const objectValues = JSON.parse(getValues)
// setDetails(objectValues)
// getValues.forEach(object => {
// console.log("id:", object.id)
// setToyId(object.id)
// })
}
}catch(error){
console.log('getData didnt work')
}
}
const [cardholder, setCardholder] = useState('');
const [cardNumber, setCardNumber] = useState('');
const [expiryDate, setExpiryDate] = useState('');
const [cvc, setCvc] = useState('');
const [allItems, setAllItems] = useState(0);
const [total, setTotal] = useState(0)
const clearCart = () => {
console.log('cc')
}
const countAllItems = () => {
console.log('cai')
}
const countTotal = () => {
console.log('ct')
}
return (
<ScrollView style={{backgroundColor: 'white', height: '100%'}}>
<ItemCard />
<View>
<View style={styles.payment}>
<Text>Your Shopping Cart</Text>
<View>
<Text value={allItems} onChangeText={(value)=>{setAllItems(value)}}>Overall Items: {countAllItems}</Text>
<Text value={total} onChangeText={(value)=>{setTotal(value)}}>Total Price: ${countTotal}.00</Text>
</View>
</View>
<Text>Payment</Text>
<TextInput placeholder='Cardholder Name' style={styles.inputsLong} placeholderTextColor='black' value={cardholder} onChangeText={(value)=>setCardholder(value)}/>
<TextInput placeholder='Card Number' style={styles.inputsLong} placeholderTextColor='black' value={cardNumber} onChangeText={(value)=>setCardNumber(value)}/>
<View style={styles.shortForm}>
<TextInput placeholder='MM/YY' style={styles.inputsShort} placeholderTextColor='black' value={expiryDate} onChangeText={(value)=>setExpiryDate(value)} />
<TextInput placeholder='CVC' style={styles.inputsShort} placeholderTextColor='black' value={cvc} onChangeText={(value)=>setCvc(value)}/>
</View>
<View style={styles.buttonRow}>
<TouchableOpacity>
<Text style={styles.cancelButton} onPress={clearCart}>Cancel Order</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text style={styles.orderButton} onPress={()=>navigation.navigate('ConfirmScreen')}>Place Order</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
)
}
export default Cart
const styles = StyleSheet.create({ // styles })
ItemCard component doesn't take any parameters yet because I'm having problems storing data and passing data between pages, but this is what the component looks like in its current state:
import { StyleSheet, Text, View, ScrollView } from 'react-native'
import React from 'react'
const ItemCard = () => {
return (
<ScrollView>
<View style={styles.itemSection}>
<View style={styles.card}></View>
</View>
</ScrollView>
)
}
export default ItemCard
const styles = StyleSheet.create({ // styles })
For this warning
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
you have to call the functions
eg. countTotal()
<Text value={total} onChangeText={(value)=>{setTotal(value)}}>Total Price: ${countTotal()}.00</Text>
if you are using onPress then like this
<Text style={styles.cancelButton} onPress={()=>clearCart()}>Cancel Order</Text>

Confusion with React Native Navigation

I'm building a React Native gig guide app, using stack navigation to get around the different screens.
So here's what I want to happen - the user can navigate from Map.js to List.js, where they're presented with a list of gigs. I want them to be able to tap on a gig and be directed to GigDetails.js, where they can see the details about that specific gig.
Users also have the option of tapping on a gig marker in Map.js which also directs them to GigDetails.js. How do I configure my routing so users can get to GigDetails from both Map.js and List.js?
Currently, Map-to-details is working, but I don't know how to go from List-to-details.
App structure is as follows (route with asterisk is the part I'm having trouble with:
screens/List.js <-----> screens/Map.js (Home screen)
*| |
*| | (GigDetails navigated to via map Marker Callout)
*| |
*GigDetails.js GigDetails.js
Code:
App.js
import 'react-native-gesture-handler';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { MyStack } from './routes/homeStack';
export default function App() {
return (
<NavigationContainer>
<MyStack/>
</NavigationContainer>
);
}
HomeStack.js
import { createStackNavigator } from "#react-navigation/stack";
import List from '../screens/List'
import Map from '../screens/Map'
import Header from "../components/Header";
import GigDetails from "../screens/GigDetails";
const Stack = createStackNavigator()
export const MyStack = () => {
return (
<Stack.Navigator
initialRouteName="Map"
>
<Stack.Screen
name="Map"
component={Map}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
<Stack.Screen
name="List"
component={List}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
<Stack.Screen
name="GigDetails"
component={GigDetails}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
</Stack.Navigator>
);
};
Map.js
import { StyleSheet,View,Text,Pressable } from 'react-native'
import GigMap from '../components/GigMap'
const Map = ({ navigation }) => {
return (
<View style = {styles.container}>
<GigMap navigation = {navigation}/>
<View style = {styles.footer}>
<Pressable
title = "Go to list view"
onPress = {() => navigation.navigate("List")}
style = {styles.button}
>
<Text style = {styles.buttonText}>List View</Text>
</Pressable>
</View>
</View>
)
}
GigMap.js
import { useState,useEffect } from 'react';
import { StyleSheet, Text, View,Pressable } from 'react-native';
import MapView from 'react-native-maps';
import { Marker,Callout } from 'react-native-maps';
import { query,collection,getDocs } from 'firebase/firestore';
import { db } from '../firebase';
import CalloutView from './CalloutView';
import { mapStyle } from '../util/mapStyle';
import dayjs from 'dayjs';
const GigMap = ({ navigation }) => {
const [gigs, setGigs] = useState([]);
const [ date,setDate ] = useState(dateToday)
const [ daysAdded,setDaysAdded ] = useState(1)
//Generating current date
const addHours =(numOfHours, date = new Date()) => {
date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000);
return date;
}
let localDate = addHours(13)
useEffect(() => {
setDate(localDate)
},[])
const addDay = () => {
setDaysAdded(daysAdded+1)
localDate.setDate(localDate.getDate() + daysAdded)
setDate(localDate)
}
console.log(date)
const day = new Date().getDate();
const month = new Date().getMonth() + 1;
const year = new Date().getFullYear();
const dateToday = `${day}/${month}/${year}`;
//Making call to Firebase to retrieve gig documents from 'gigs' collection
useEffect(() => {
const getGigs = async () => {
try {
const gigArray = [];
const q = query(collection(db, "gigs"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) =>
gigArray.push({ id: doc.id, ...doc.data() })
);
setGigs(gigArray);
} catch (err) {
console.log(`Error: ${err}`);
}
};
getGigs();
}, []);
//Filtering through gigs to return only current day's gigs
const gigsToday = gigs.filter((gig) => gig.date === dateToday);
return (
<View style={styles.container}>
<Text style={styles.headerText}>Today's gigs</Text>
<MapView
initialRegion={{
latitude: -41.29416,
longitude: 174.77782,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}
style={styles.map}
customMapStyle={mapStyle}
>
{gigsToday.map((gig, i) => (
<Marker
key={i}
coordinate={{
latitude: gig.location.latitude,
longitude: gig.location.longitude,
}}
image={require("../assets/Icon_Gold_48x48.png")}
>
<Callout
style={styles.callout}
onPress={() =>
navigation.navigate("GigDetails", {
venue: gig.venue,
date: gig.date,
gigName: gig.gigName,
time: gig.time,
})
}
>
<CalloutView
venue={gig.venue}
date={gig.date}
gigName={gig.gigName}
time={gig.time}
style={styles.calloutView}
/>
</Callout>
</Marker>
))}
</MapView>
<View style = {styles.buttonOptions}>
<Pressable >
<Text style = {styles.buttonOptionsText}>previous day's gigs</Text>
</Pressable>
<Pressable onPress = {addDay}>
<Text style = {styles.buttonOptionsText}>next day's gigs</Text>
</Pressable>
</View>
</View>
);
};
List.js
import { View,Text } from 'react-native'
import ListByDay from '../components/ListByDay';
const List = () => {
return (
<View>
<ListByDay/>
</View>
)
}
export default List;
ListByDay.js
import { StyleSheet, Text, View,FlatList,TouchableOpacity } from 'react-native';
import { useGigs } from '../hooks/useGigs';
const ListByDay = ({ navigation }) => {
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
test: {
borderWidth:1,
borderColor:'black',
},
header: {
padding:10
}
})
export default ListByDay;
The component ListByDay currently tries to destructure the navigation object from its props. However, the navigation object is passed by the react-navigation framework to the components that are defined as screens inside a navigator. This is not the case for ListByDay. Thus, you cannot access the navigation object as you are currently trying to do.
There are multiple options.
1) Pass the navigation object as a prop from its parent which is a screen inside a navigator
Since List is the parent component for ListByDay and List is an actual screen defined in your stack navigator, it will receive the navigation object as a prop from the react-navigation framework. You can access it and pass it to its children.
const List = ({navigation}) => {
return (
<View>
<ListByDay navigation={navigation} />
</View>
)
}
Then, implement the navigation logic in the onPress function of your TouchableOpacity inside ListByDay.
const ListByDay = ({ navigation }) => {
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test} onPress={() => navigation.navigate(...)}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
2) Use the useNavigation hook
If you do not want to pass the navigation object down to ListByDay, then you can always use the useNavigation hook instead.
const ListByDay = () => {
const navigation = useNavigation()
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test} onPress={() => navigation.navigate(...)}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}

React Native with Expo app not rendering FlatList

I have no errors showing but I am getting a blank page when my app runs. It started rendering a blank page when started putting some api code which I copied and pasted from another project where it worked exactly perfect but for some reason, it is refusing to work here This is how my code looks like. This is my App.js file
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import Screen from './app/components/Screen'
import ProductScreen from './app/screens/ProductScreen';
export default function App() {
return (
<Screen>
<ProductScreen />
</Screen>
)
}
Then this is the product screen.js
import React, {useState, useEffect} from 'react'
import { FlatList, StyleSheet, ActivityIndicator, Text, View } from 'react-native'
import Card from '../components/Card';
export default function ProductScreen() {
const [products, setproducts] = useState([]);
const [loading, setloading] = useState(true);
const getProducts = async () => {
try {
const response = await fetch('https://fakestoreapi.com/products/1');
const data = await response.json();
setproducts(data);
} catch (error) {
console.log("Something went wrong in your code",error)
} finally {
setloading(false);
}
}
useEffect(() => {
getProducts();
}, []);
return (
<View>
{loading ? <ActivityIndicator/> : (
<FlatList
data={products}
keyExtractor={(id) => id}
renderItem={({item}) => (
<Card
title={item.title}
subtitle={item.description}
img={item.image}
/>
)}
/>
)}
</View>
)
}
const styles = StyleSheet.create({})
And lastly the card.js file
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import AppText from './AppText';
export default function Card({title, subtitle, img}) {
return (
<View style={styles.container}>
<Image source={img} style={styles.image} />
<View style={styles.cardText}>
<AppText style={{color: "black"}}>{title}</AppText>
<AppText style={{color: "#4ecdc4"}}>{subtitle}</AppText>
</View>
</View>
)
}
Where could I be going wrong?
Well there are several issues in your Expo Snack. First your expression of:
{loading && <FlatList />}
is wrong because you're setting it to check for loading of true when after you retrieve your data in getProducts you set it to false.
Was able to get it to work, again, with:
import React, { useState, useEffect } from 'react'
import { FlatList, StyleSheet, View } from 'react-native'
import Card from '../components/Card'
export default function ProductScreen() {
const [products, setProducts] = useState([])
const [loading, setLoading] = useState(true)
const getProducts = async () => {
try {
const response = await fetch('https://fakestoreapi.com/products/5')
const data = await response.json()
setProducts([data])
setLoading(false)
} catch (error) {
console.log('Something went wrong in your code', error)
}
}
useEffect(() => {
getProducts()
}, [])
return (
<View style={styles.container}>
{!loading && products.length !== 0 && (
<View>
<FlatList
data={products}
keyExtractor={(_, id) => id.toString()}
renderItem={({ item }) => <Card title={item.title} subtitle={item.description} img={item.image} />}
/>
</View>
)}
</View>
)
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'lightgrey',
flex: 1,
},
})
Another error exists in Card for Image. Per memory when passing an image URL it should be set with a URI:
<Image source={{ uri: img }} style={styles.image} />
Another issue you'll face is in ProductScreen the View style for container should have a flex: 1 applied as indicated in the code above.
Looking at the code for snack.expo.dev/#vuyi/spontaneous-candy it seems your missing the conditional rendering in your return for the ProductScreen.
Your code looks like this:
<View style={styles.container}>
{loading &&
<FlatList
data={[products]}
keyExtractor={(_, id) => id.toString()}
renderItem={({item}) =>
<Card
title={item.title}
subtitle={item.description}
img={item.image}
/>
}
/>
}
</View>
When it needs to look something like this:
<View style={styles.container}>
{loading ? null :
<FlatList
data={[products]}
keyExtractor={(_, id) => id.toString()}
renderItem={({item}) =>
<Card
title={item.title}
subtitle={item.description}
img={item.image}
/>
}
/>
}
</View>
Adding that conditional will prevent your Flatlist component from rendering before it has access to the data you are fetching. It looks like you had it right in the original post but not in the Expo Snack environment.

How to see the full details of particular member when it is clicked after search?

I have build a search engine and I am able to fetch data successfully the only thing I want to do is whenever that searched field is selected, it will show the details. (There are further details are also present like age,house_no etc.) It is like another screen have to come with full details.
I am stuck here, please help me out. It is like navigation to another screen with full details for now I am using alert command to show the some details I want another screen please. Thanks.
import React, {useState, useEffect} from 'react';
import {
SafeAreaView,
Text,
StyleSheet,
View,
FlatList,
TextInput,
} from 'react-native';
import { db } from '../firebase';
const App = () => {
const [search, setSearch] = useState('');
const [filteredDataSource, setFilteredDataSource] = useState([]);
const [masterDataSource, setMasterDataSource] = useState([]);
useEffect(() => {
const fetchData = async () => {
const data = await db
.collection('Data')
.get();
setFilteredDataSource(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
setMasterDataSource(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
fetchData();
}, []);
const searchFilterFunction = (text) => {
if (text) {
const newData = masterDataSource.filter(
function (item) {
const itemData = item.FM_NAME_EN + item.EPIC_NO + item.MOBILE_NO
? item.FM_NAME_EN.toUpperCase() + item.EPIC_NO.toUpperCase() + item.MOBILE_NO
: ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilteredDataSource(newData);
setSearch(text);
} else {
setFilteredDataSource(masterDataSource);
setSearch(text);
}
};
const ItemView = ({item}) => {
return (
<Text
style={styles.itemStyle}
onPress={() => getItem(item)}>
{item.FM_NAME_EN}
{'\n'}
{item.GENDER.toUpperCase()}
</Text>
);
};
const ItemSeparatorView = () => {
return (
// Flat List Item Separator
<View
style={{
height: 0.5,
width: '100%',
backgroundColor: '#C8C8C8',
}}
/>
);
};
const getItem = (item) => {
alert('Name : ' + item.FM_NAME_EN + ' Epic_no : ' + item.EPIC_NO);
};
return (
<SafeAreaView style={{flex: 1}}>
<View style={styles.container}>
<TextInput
style={styles.textInputStyle}
onChangeText={(text) => searchFilterFunction(text)}
value={search}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={filteredDataSource}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={ItemSeparatorView}
renderItem={ItemView}
/>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
},
itemStyle: {
padding: 10,
},
textInputStyle: {
height: 40,
borderWidth: 1,
paddingLeft: 20,
margin: 5,
borderColor: '#009688',
backgroundColor: '#FFFFFF',
},
});
export default App;
first of all, you have to create ItemDetails.js as the new component for the new screen:
import React from 'react';
import {Text, View} from 'react-native';
const ItemDetails= () => {
return (
<View>
<Text>ItemDetails screen</Text>
</View>
);
};
export default ItemDetails;
then, make sure to install react native navigation and go to create a new screen:
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="ItemDetails" component={ItemDetails} />
</Stack.Navigator>
then get back to the app component and modify your getItem function to this:
const getItem = item => {
navigation.navigate('ItemDetails', {item});
};
this is how you tell the application that it has to move to another screen, and you sent the specific item with it, now let's modify our ItemDetails.js to see the data:
const ItemDetails = ({route}) => {
//console.log(route.params) so you can know how to access whatever you want to print
return (
<View>
<Text>{route.params.item.item.FM_NAME_EN}</Text>
</View>
);
};
If I understood your question correctly, you need another route:
First, create a component for the new page that will display the details:
ItemDetails.js:
import { useParams } from 'react-router-dom' //You will get the params from the url
import {useState, useEffect} from 'react'
export const ItemDetails = props => {
const { id } = useParams()
const [item, setItem] = useState(null)
useEffect(() => {
if(id){
const itemSelected = /* get your item by id */
setItem(itemSelected)
}
}, [id])
return(
<p> {item.name} </p>
)
}
Wherever you define your routes:
import {ItemDetails} from './path/to/ItemDetails/'
<Switch>
...
<Route exact path="/item/:id" component={ItemDetails} />
...
<Switch>
Lastly, wrap your jsx with react-router link:
import { Link } from 'react-router-dom'
...
return (
<Link to={`/item/${item.id}`}>
<Text
style={styles.itemStyle}
{item.FM_NAME_EN}
{'\n'}
{item.GENDER.toUpperCase()}
</Text>
</Link>
);

react native navigation - update list in screen on add/update/delete items

I am building a mobile app using react native. I am using the stack navigator from react-native-navigation library.
the stack has two screens, the first screen ItemsListScreen displays a list of items with add button, clicking the add button navigates to the second screen AddItemScreen which displays some inputs for the item with a save button. clicking the save button will navigate back to the list screen.
Items are saved currently on a local SQLite database, but in future will be saved on a server.
ItemsListScreen.js
import React, { useEffect, useState, useContext } from "react";
import { Button, Image, Platform, StyleSheet, View, TouchableOpacity } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import { Ionicons } from "#expo/vector-icons";
import DatabaseContext from "../db/DatabaseContext";
export default function ItemsListScreen({ navigation }) {
const db = useContext(DatabaseContext);
const [items, setItems] = useState([]);
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={() => navigation.navigate("AddItem")}>
<Ionicons name="md-add" size={30} />
</TouchableOpacity>
);
},
});
db.getItems().then((data) => {
setItems(data);
});
}, []);
return (
<View style={styles.container}>
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
{items.map((item, index) => (
<View key={index} style={styles.item}>
<Text style={styles.subSection}>{item.title}</Text>
</View>
))}
</ScrollView>
</View>
);
}
AddItemScreen.js
import React, { useState, useEffect, useContext } from "react";
import { StyleSheet, View, Dimensions, TextInput, ScrollView, TouchableOpacity, Picker, Switch } from "react-native";
import DateTimePicker from "#react-native-community/datetimepicker";
import { Ionicons } from "#expo/vector-icons";
import DatabaseContext from "../db/DatabaseContext";
export default function AddItemScreen({ navigation }) {
const db = useContext(DatabaseContext);
const [item, setItem] = useState({
title: "",
description: "",
// more properties
});
const saveItem = () => {
db.saveItem(item);
navigation.navigate("ItemsList");
};
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={saveItem}>
<Ionicons name="md-save" size={30} />
</TouchableOpacity>
);
},
});
}, [item]);
return (
<View style={styles.scene}>
<ScrollView>
<Text style={styles.label}>Title</Text>
<TextInput style={styles.field} value={item.title} onChangeText={(text) => setItem((prevState) => ({ ...prevState, title: text }))} />
<Text style={styles.label}>Description</Text>
<TextInput style={styles.field} value={item.description} onChangeText={(text) => setItem((prevState) => ({ ...prevState, description: text }))} />
{/* more inputes */}
</ScrollView>
</View>
);
}
My problem is that the list on ItemsListScreen is not updated when a new item is added. I need to reload the app to get the list updated.
I tried to remove the second parameter (the empty array) from the useEffect in ItemsListScreen, it works but I think it is a bad solution as it keeps reading from the db on each re-render.
How can I refresh the list whenever the user navigates back to the list screen? I thought the useEffect will be executed whenever the screen is activated but it seems not.
You will be resolved by adding navigation focus listener to ItemsListScreen.js. Replace your useEffet in this way
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<TouchableOpacity activeOpacity={0.5} style={{ paddingRight: 10 }} onPress={() => navigation.navigate("AddItem")}>
<Ionicons name="md-add" size={30} />
</TouchableOpacity>
);
},
});
const unsubscribe = navigation.addListener('focus', () => {
db.getItems().then((data) => {
setItems(data);
});
});
return unsubscribe;
}, []);

Resources