React Native with Expo app not rendering FlatList - reactjs

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.

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>

Fade out loading screen in React Native

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>
);

ReactNative - render a collection of children using array error when rendering userInfo in FBSDK?

I am trying to build facebook signIN in functional component and facing below error:
ERROR - Objects are not valid as a React child (found: object with keys {data}). If you meant to render a collection of children, use an array instead.
Following is code snippet:
import React, { Fragment, useEffect, useState } from 'react';
import {SafeAreaView, ScrollView, StatusBar, StyleSheet,Text, TouchableOpacity, View,Image,Button}
from 'react-native';
import { AccessToken, LoginButton } from 'react-native-fbsdk';
const App = () => {
const [loggedIn, setLoggedIn] = useState(false);
const [fbUserInfo, setFbUserInfo] = useState({data: ''});
return (
<Fragment>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<View style={styles.body}>
<View style={styles.sectionContainer}>
<LoginButton
onLoginFinished={(error, result) => {
if (error) {
console.log('login has error: ' + result.error);
} else if (result.isCancelled) {
console.log('login is cancelled.');
} else {
console.log(result);
AccessToken.getCurrentAccessToken().then((data) => {
//setLoggedIn(true);
setFbUserInfo(data.userID);
console.log("Login", data, data.accessToken.toString());
});
}
}}
onLogoutFinished={() => {
//setLoggedIn(false);
setFbUserInfo({data:''});
}}
/>
</View>
<View style={styles.buttonContainer}>
{!loggedIn && (
<Text>You are currently logged out</Text>
)}
</View>
{(
<View>
<View style={styles.listHeader}>
<Text>User Info</Text>
</View>
<View style={styles.detailContainer}>
<Text style={styles.title}>ID</Text>
<Text style={styles.message}>{fbUserInfo}</Text>
</View>
</View>
)}
</View>
</ScrollView>
</SafeAreaView>
</Fragment>
);
};
export default App;
Please help me to resolve an issue, thanks
Try this
AccessToken.getCurrentAccessToken().then((data) => {
const { accessToken } = data;
fetch('https://graph.facebook.com/v2.5/me?fields=email,name,friends&access_token=' + accessToken)
.then((response) => response.json())
.then((json) => {
// Some user object has been set up somewhere, build that user here
const userID = json.id;
//setLoggedIn(true);
setFbUserInfo({data: userID});
})
.catch(() => {
reject('ERROR GETTING DATA FROM FACEBOOK')
});
});
I resolved it by using this;
const [fbUserInfo, setFbUserInfo] = useState('');
onLogoutFinished={() => {
setLoggedIn(false);
setFbUserInfo('');
}}

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;
}, []);

where am I breaking hook rules?

import { StyleSheet, Text, View } from 'react-native'
import * as firebase from 'firebase';
//firebaseinititalized//
const[message,setMessage]=useState('');
const [messages, setMessages] = useState([])
useEffect(() => {
firebase.database.ref().child('messages').once('value',
snapshot=>{
const data=snapshot.val()
if(snapshot.val()){
const initMessages=[];
Object.
keys(data).
forEach(message=>initMessages.push(data[message]));
(initMessages)=>setMessages([initMessages]);
}
}
)
firebase.database.ref().child('messages').on("child_added",
snapshot=>{
const data=snapshot.val()
if(snapshot.val()){
(prevMessages)=> setMessages([data,...prevMessages]);
}
}
)
}, [])
const addItem =()=>{
if(!message) return;
const newMessage=firebase.database.ref().child().push();
newMessage.set(message,()=>setMessage(''));
}
function Hookfire() {
return (
<View >
<View >
<TextInput placeholder=" enter text message"
value={message} onChangeText={text=>setMessage(text)}/>
<Button title="send" onPress={addItem}/>
</View>
<FlatList data={messages}
renderItem={({item})=>
<View >
<Text >{item}</Text>
</View>}
/>
</View>
)
}
export default Hookfire
const styles = StyleSheet.create({})
error message shows that:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
you have move the hook call inside the HookFire function.
import { StyleSheet, Text, View } from 'react-native'
import * as firebase from 'firebase';
function Hookfire() {
const[message,setMessage]=useState('');
const [messages, setMessages] = useState([])
useEffect(() => {
firebase.database.ref().child('messages').once('value',
snapshot=>{
const data=snapshot.val()
if(snapshot.val()){
const initMessages=[];
Object.
keys(data).
forEach(message=>initMessages.push(data[message]));
(initMessages)=>setMessages([initMessages]);
}
}
)
firebase.database.ref().child('messages').on("child_added",
snapshot=>{
const data=snapshot.val()
if(snapshot.val()){
(prevMessages)=> setMessages([data,...prevMessages]);
}
}
)
}, [])
const addItem =()=>{
if(!message) return;
const newMessage=firebase.database.ref().child().push();
newMessage.set(message,()=>setMessage(''));
}
return (
<View >
<View >
<TextInput placeholder=" enter text message"
value={message} onChangeText={text=>setMessage(text)}/>
<Button title="send" onPress={addItem}/>
</View>
<FlatList data={messages}
renderItem={({item})=>
<View >
<Text >{item}</Text>
</View>}
/>
</View>
)
}
export default Hookfire;

Resources