React Native Deck Swiper - reactjs

I am trying to make a GET request to an enpoint using the two functional components below to display in my react native deck swipper
//using fetch
const getDataUsingFetch = () => {
fetch(latestNews+ApiKey)
.then((response) => response.json())
.then((responseJson) => {
// set the state of the output here
console.log(responseJson);
setLatestNews(responseJson);
})
.catch((error) => {
console.error(error);
});
}
//using anxios
//asynchronous get request call to fetech latest news
const getDataUsingAnxios = async () => {
//show loading
setLoading(true);
setTimeout(async () => {
//hide loading after the data has been fetched
setLoading(false);
try {
const response = await axios.get(latestNews+ApiKey);
setLatestNews(response.data);
setLoading(false);
console.log(getLatestNews);
} catch (error) {
// handle error
alert(error.message);
}
}, 5000);
};
Data returned when logged from console:
Array [
Object {
"category_id": "8",
"content": "Hi",
"created_at": "2020-11-12T12:43:03.000000Z",
"featured_image": "splash-background_1605184983.jpg",
"id": 19,
"news_url": "doerlife.com",
"title": "I m good how about you",
"updated_at": "2020-11-12T12:43:03.000000Z",
}....]
I now save the data into a state array
const [getLatestNews, setLatestNews] = useState([]);
Here is my swipper(some code ommited - not necessary)
<Swiper
ref={useSwiper}
//cards={categoryID(docs, "2")}
cards={getLatestNews}
cardIndex={0}
backgroundColor="transparent"
stackSize={2}
showSecondCard
cardHorizontalMargin={0}
animateCardOpacity
disableBottomSwipe
renderCard={(card) => <Card card={card} />}
.....
When I try to access any data in the array from my Card reusable component, e.g card.featured_image
I WILL GET THIS ERROR - TypeError: undefined is not an object (evaluating 'card.featured_image').
PLEASE CAN SOMEONE HELP ME.
//Card reusable component for deck swipper
import React from 'react'
import { View, Text, Image, ImageSourcePropType } from 'react-native'
import styles from './Card.styles'
const Card = ({ card }) => (
<View activeOpacity={1} style={styles.card}>
<Image
style={styles.image}
source={card.featured_image}
resizeMode="cover"
/>
<View style={styles.photoDescriptionContainer}>
<Text style={styles.title}>{`${card.title}`}</Text>
<Text style={styles.content}>{`${card.content}`}</Text>
<Text style={styles.details}>
Swipe Left to read news in details
</Text>
</View>
</View>
);
export default Card

I've done something similar to this before so I think I can help a bit. The problem here is that your getLatestNews state has not been updated yet before the cards render. You can fix the problem by having another state called "isDataReturned". Then, have a useEffect that triggers whenever getLatestNews's length changes. If getLatestNews's length is > 0, then you can set isDataReturned to be true and render the deck only when isDataReturned is true.
Here's a code sample that I made:
const [getLatestNews, setLatestNews] = useState([]);
const [dataIsReturned, setDataIsReturned] = useState(false)
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://cat-fact.herokuapp.com/facts',
);
setLatestNews(result.data);
};
fetchData();
}, []);
useEffect(() => {
if (getLatestNews.length > 0) {
setDataIsReturned(true)
} else {
setDataIsReturned(false)
}
}, [getLatestNews.length])
if( dataIsReturned === true) {
return (
<View style={styles.container}>
<Swiper
cards={getLatestNews}
renderCard={(card) => {
return (
<View style={styles.card}>
<Text>{card.text}</Text>
</View>
)
}}
onSwiped={(cardIndex) => {console.log(cardIndex)}}
onSwipedAll={() => {console.log('onSwipedAll')}}
cardIndex={0}
backgroundColor={'#4FD0E9'}
stackSize= {3}>
</Swiper>
</View>)
} else {
return(<Text>Loading</Text>)
}

In the renderCard attribute, i changed it from
renderCard={(card) => <Cardz card={card} />}
to
renderCard={(card) => (card && <Cardz card={card} />) || null}
and it worked.

Related

Render component from array values in React Native

I'm trying to render component/function from array values.
Main function
const GeneratedHistory = () => {
return (
<View style={styles.container}>
<View style={styles.headerWrapper}>
<Text variant="headlineLarge" style={styles.headersText}>Historia</Text>
<Text variant='labelMedium'>Generowane kody</Text>
</View>
<View style={styles.mainWrapper}>
<ScrollView>
{getItems()}
</ScrollView>
</View>
</View>
I retrieving values from Firestore and saves what i want to array named Items.
function getItems() {
const items = [];
try {
firebase.firestore().collection("Generated").where("username", "==", auth.currentUser.email)
.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
items.push({
qrImage: doc.get("qrImage"),
qrText: doc.get("qrText"),
time: doc.get("time"),
})
});
items.map((item) => {
console.log(item.qrText)
})
});
} catch (error) {
alert('Error occured')
}
}
Nextly i map the array, printing to console and trying to render function named SingleElement.
function singleElement(text) {
return (
{text}
)
}
Logging to console work's fine, but i can't render the function.
Screen just stays white.
So, I have to use async function, in my case, I fetch the data when the window opens and save it to array.
useEffect(() => {
async function fetchData() {
todoRef
.onSnapshot(
querySnaphsot => {
const items = []
querySnaphsot.forEach((doc) => {
const { qrImage, qrText, time } = doc.data()
items.push({
id: doc.id,
qrImage,
qrText,
time,
})
setItems(items);
})
}
)
} fetchData()
}, [])
Then I map the elements and display them in the component.
items.map((item) => {
return <YourComponent key={item.id} text={item.qrText} time={item.time}>
</YourComponent>
})
}

Objects are not valid as a React child, use an array instead

I am trying to render the first and last name from a json request using axios.
I am getting the following error you see in the title. I have included a snack example here reproducing the error exactly as well as added the code below.
Thank you
const plsWork = () => {
// Make a request for a user with a given ID
return axios.get('https://randomuser.me/api')
.then(({data}) => {
console.log(data);
return data
})
.catch(err => {
console.error(err);
});
}
const userName = (userInfo) => {
const {name: {first, last}} = userInfo;
return {first}, {last};
}
export default function App() {
const [data, setData] = React.useState(' ')
const [userInfos, setUserInfos] = React.useState([]);
React.useEffect(() => {
plsWork().then(randomData => {
setData(JSON.stringify(randomData, null, 4) || 'No user data found.')
setUserInfos(randomData.results)
})
}, []);
return (
<View>
<ScrollView>
{
userInfos.map((userInfo, idx) => (
<Text key={idx}>
{userName(userInfo)}
</Text>
))
}
<Text style={{color: 'black', fontSize: 15}}>
{data}
</Text>
</ScrollView>
</View>
);
}
You have to return a React Component in the userName function.
In the line 21:
Change from return {first}, {last} to return <>{first}, {last}</>.
It should work!
Here is code edited: snack expo

Get data from API by map function

I'm running into a problem that I've been working on for days and unfortunately I can't figure it out by myself. I'm trying to create a View which shows some information from an API. But every time I map this item, I want to do another API call which checks the live price of that product.
So I have for example some JSON data what I get from an API.
{
"id": 1,
"name": "test product",
"productid": "73827duf"
},
{
"id": 2,
"name": "test product2",
"productid": "734437dde"
}
So I show this data with the following code inside my application:
{item.products.map((products) => {
return (
<View
key={products.id}
>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10,
}}
>
{products.name}
{getProductPriceJumbo(
products.productid
)}
</Text>
</View>
);
})}
So I want to run every time a function which fetches data from another API. I'm sending the productID because that's the only information I need to call this API. You can see this function down below:
function getProductPriceJumbo(id) {
fetch("https://---/test.php?id=" + id + "/", {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
return data[0].price;
});
}
So this fetch returns a big list with information about the product from a third party API. I only want to return the price, that's the reason why I only return the price value and I want to print this out on the view above. I can't really figure out how to do this. I get undefined from the function every time I run it. Hope someone can help me with this.
Create a new Price Component to display the price
function Price({ id }) {
const [price, setPrice] = useState(0);
useEffect(() => {
function getProductPriceJumbo(id) {
fetch("https://---/test.php?id=" + id + "/", {
method: "GET"
})
.then((response) => response.json())
.then((data) => {
setPrice(data[0].price);
});
}
getProductPriceJumbo(id);
},[]);
return <Text>{price}</Text>;
}
And your .map will become
{
item.products.map((products) => {
return (
<View key={products.id}>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{products.name}
<Price id={products.productid} />
</Text>
</View>
);
});
}
The reason you are getting undefined is because the window is rendering before the function finishes running. You will have define an asynchronous function before you return your view.
const [data, setData] = useState([])
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get('API URL');
setData(response);
} catch (error) {
console.error(error.message);
}
setLoading(false);
}
fetchData();
}, []);
Then you can use data[0].price;
You'll probably want to make your individual product into its own component that handles the fetching, and setting the price to a state value that's local to that product view. Here's a full example of how you could do that:
import { useState, useEffect } from "react";
const Product = ({ product }) => {
const [price, setPrice] = useState("Price loading...");
useEffect(() => {
fetch("https://---/test.php?id=" + product.productid + "/", {
method: "GET"
})
.then((response) => response.json())
.then((data) => {
setPrice(data[0].price);
});
}, [product]);
return (
<View>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{product.name}
{price}
</Text>
</View>
);
};
const App = () => {
const item = {
products: [
{
id: 1,
name: "test product",
productid: "73827duf"
},
{
id: 2,
name: "test product2",
productid: "734437dde"
}
]
};
return (
<div>
{item.products.map((product) => (
<Product key={product.id} product={product} />
))}
</div>
);
};
Alternatively, you could use Promise.all to get all of the price values before mapping your products:
import { useState, useEffect } from "react";
const App = () => {
const [item] = useState({
products: [
{
id: 1,
name: "test product",
productid: "73827duf"
},
{
id: 2,
name: "test product2",
productid: "734437dde"
}
]
});
const [products, setProducts] = useState([]);
useEffect(() => {
Promise.all(
item.products.map(async (product) => {
const response = await fetch(
`https://---/test.php?id=${product.productid}/`
);
const data = await response.json();
return {
...product,
price: data[0].price
};
})
).then((products) => setProducts(products));
}, [item]);
return (
<div>
{products.map((product) => {
return (
<View key={product.id}>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{product.name}
{product.price}
</Text>
</View>
);
})}
</div>
);
};

React Native FlatList flickers lazy loading additional data

I have a fairly basic FlatList component implemented using hooks. The list simply loads data from a random user API and lazy loads additional data via infinite scroll. The only visual issue I'm experiencing is that when I merge the new data with the current, the new data being appended flickers very briefly before fully rendering. Not sure what could be causing this.
Expo Snack
const useRestApi = (url) => {
const [ data, setPeople ] = useState([]);
const [ page, setPage ] = useState(1);
const [ results, setResults ] = useState(20);
const [ loading, setLoading ] = useState(false);
useEffect(() => {
const fetchPeople = async () => {
setLoading(true);
const response = await fetch(`${url}&page=${page}&results=${results}`);
const json = await response.json();
if(page !== 1)
setPeople([...data, ...json.results]);
else
setPeople(json.results);
setLoading(false);
}
fetchPeople();
}, [page]);
return [{data, loading, page}, setPage, setResults];
}
const App: () => React$Node = () => {
const [{ data: people, loading, page }, setPage, setResults] = useRestApi(`https://randomuser.me/api?&seed=ieee`);
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<FlatList
data={people}
onEndReachedThreshold={0.2}
keyExtractor={(item, index) => index.toString()}
renderItem={({item, index}) => (
<View key={index} style={styles.listItem}>
<Text style={styles.listItemHeader}>{item.name.first} {item.name.last}</Text>
<Text style={styles.listItemSubHeader}>{item.location.country}</Text>
<Text style={styles.listItemBody}>{item.location.street.number} {item.location.street.name}</Text>
<Text style={styles.listItemBody}>{item.location.city} {item.location.state} {item.location.postcode}</Text>
</View>
)}
refreshing={loading}
onRefresh={() => {setResults(20); setPage(1);}}
onEndReached={() => {setResults(5); setPage(page + 1);}}
ItemSeparatorComponent={() => ItemSeparatorComponent}
ListFooterComponent={() => loading ? ListFooterComponent : null}
/>
</SafeAreaView>
</>
);
};

How to use Infinite Scroll with FlatList and React Hook?

I'm refactoring to React Hooks but I can't get Infinite Scroll with FlatList working.
const [page, setPage] = useState(1);
This is my useEffect Hook:
useEffect(() => {
const loadProducts = async () => {
setIsLoading(true);
let response = await fetch(`${api}&page=${page}&perPage=5`);
let results = await response.json();
setProducts([...products, ...results.data]);
setIsLoading(false);
};
loadProducts();
}, [page]);
Offset is ${page}, limit is &perPage=5 (hardcoded to 5)
Flatlist:
<FlatList
data={products}
keyExtractor={(item) => item.id}
renderItem={renderGridItem}
onEndReached={loadMore}
onEndThreshold={0.3}
/>;
LoadMore:
const loadMore = () => {
setPage(page + 1);
};
In theory, this should work, shouldn't it?
Description
I was struggling a lot with this myself. Here's an example using a SectionList (basically the same as a Flatlist)
The header numbers indicates the request number send to the API. You can check that the request are in the correct order and that there are no duplicates, by clicking the "Check Numbers" button.
In this example we use reqres.in to simulate a fetch to some data.
The example also implements pull-to-refresh. Again, you can check that the length of the array is as expected after a pull-to-refresh by clicking the "Check length" button.
Expo snack
A snack of the example can be found here: https://snack.expo.io/BydyF9yRH
Make sure to change platform to iOS or Android in the snack (Web will not work)
Code
import * as React from 'react';
import { ActivityIndicator } from 'react-native'
var _ = require('lodash')
import {
StyleSheet,
Text,
View,
SafeAreaView,
SectionList,
Button,
RefreshControl
} from 'react-native';
function Item(item) {
return (
<View style={styles.item}>
<Text style={styles.title}>{item.title.first_name}</Text>
</View>
);
}
export default function testSectionList({ navigation }) {
const [data, setData] = React.useState()
const [loading, setLoading] = React.useState(true)
const [refreshing, setRefreshing] = React.useState(false);
const [showRefreshingIndicator, setShowRefreshingIndicator] = React.useState(false);
const dataIndex = React.useRef(0);
const totalHits = React.useRef(42); // In real example: Update this with first result from api
const fetchData = async (reset: boolean) => {
if (reset === true) dataIndex.current = 0;
// Make sure to return if no more data from API
if (dataIndex.current !== 0 && dataIndex.current >= totalHits.current) return []
// For example usage, select a random page
const fakepage = Math.round(Math.random()) * 2
const resultObject = await fetch(`https://reqres.in/api/users?page=${fakepage}`);
const result = await resultObject.json()
dataIndex.current++;
return {
title: `${dataIndex.current-1}`,
data: await result.data
}
}
const count = () => {
alert(data.length)
}
const checkPageNumbers = () => {
const numbers = data.map(item => parseInt(item.title))
const incremental = [...Array(data.length).keys()]
alert(_.isEqual(numbers, incremental))
}
const getInitialData = async () => {
const list = await fetchData(false)
if(!list) return
setData([list])
setLoading(false)
}
React.useEffect(() => {
getInitialData()
}, [])
const onEndReached = async () => {
const newItems = await fetchData(false)
if(!newItems.data.length) return
setData([...data, newItems])
}
const onRefresh = React.useCallback(async () => {
setShowRefreshingIndicator(true);
const newItems = await fetchData(true)
setData([newItems])
setShowRefreshingIndicator(false)
}, [refreshing]);
if (loading) return <Text>Loading...</Text>
return (
<SafeAreaView style={styles.container}>
<Button title={"Check numbers"} onPress={() => checkPageNumbers()} />
<Button title={"Check length"} onPress={() => count()} />
<SectionList
sections={data}
refreshing={refreshing}
refreshControl={
<RefreshControl refreshing={showRefreshingIndicator} onRefresh={onRefresh} />
}
onEndReached={() => {
if(refreshing) return;
setRefreshing(true)
onEndReached().then(() => {
setRefreshing(false)
})
}}
onEndReachedThreshold={1}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Item title={item} />}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.header}>{title}</Text>
)}
ListFooterComponent={<ActivityIndicator size={"large"} />}
stickySectionHeadersEnabled={false}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 40,
marginHorizontal: 16,
},
item: {
backgroundColor: '#f9c2ff',
padding: 2,
marginVertical: 2,
},
header: {
fontSize: 16,
},
title: {
fontSize: 12,
},
});
Try to use useCallback instead of useEffect on this case. Also, I've shown you how you can prevent spreading null result to setState.
const loadProducts = async () => {
setIsLoading(true);
let response = await fetch(`${api}&page=${page}&perPage=5`);
let results = await response.json();
if (result.data) {
setProducts([...products, ...results.data]);
}
setIsLoading(false);
};
useEffect(() => {
loadProducts();
}, [])
const onLoadMore = useCallback(() => {
loadProducts();
}
for more information about useCallback, please read this. https://reactjs.org/docs/hooks-reference.html#usecallback

Resources