I've been trying to output data from my Firestore db to a Flatlist in React native but so far unsuccesfull.
I've used this Flatlist for RN and Firestore docs as reference to getting started but for some reason I am missing something here regarding the output method of the Flatlist because it wont output the flatlist itself. When I console log the locations array it shows me all the docs inside that i've queried so it does push them all into an array and my understanding is of FlatLists that they need an array to function but it does not throw any error just doesn't render. Any help more than welcome!
useEffect(async () => {
const locations = [];
const querySnapshot = await getDocs(collection(db, "Location"));
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
locations.push(doc.data());
console.log(locations);
});
return () => querySnapshot();
}, []);
return (
<View style={styles.screen}>
<Text>hello</Text>
<FlatList data={locations}
renderItem={({ item }) => (
<View >
<Text>name: {item.name}</Text>
<Text>Depth: {item.depth}m</Text>
<Text>GeoLocation: {item.geo}</Text>
<Text>id: {item.uid}</Text>
</View>
)}
/>
Your variable locations is defined in your useEffect. The FlatList can not access it. You need to create a state via useState and store the data in there once it is loaded. Setting the state will cause a rerendering of the component and the FlatList will be updated with the new data.
Here is one possible implementation.
const SomeScreen = () => {
const [locations, setLocations] = useState([])
useEffect(() => {
const loadData = async () => {
const querySnapshot = await getDocs(collection(db, "Location"));
setLocations(querySnapshot.map(doc => doc.data()))
}
loadData()
}, [setLocations]);
return (
<FlatList data={locations}
renderItem={({ item }) => (
...
)}
/>
)
}
Here is a simple way:
import React, {useState} from 'react';
import { Button, View, Text, FlatList } from 'react-native';
import {query, where, collection, getDocs } from 'firebase/firestore';
import {db} from './firestoreconfig.js';
const App = () => {
const [cities, setCities] = useState([]);
async function readData()
{
setCities([]);
const mycities = [];
const q = query(collection(db, "cities"), where("capital", "==", true) );
const querySnapshot = await getDocs(q);
querySnapshot.forEach( (city) => {mycities.push({key: city.id, name: city.data().name} )})
setCities(mycities);
}
return (
<View
style = {{marginTop: 50}}
>
<Button
title='Read Data'
onPress={ () => readData()}
/>
<FlatList
data={cities}
renderItem = {
({item}) =>
<Text>{item.key} {item.name}</Text>
}
/>
</View>
);
}
export default App;
Related
Issue is when i am saving the page, the already rendered items are rerendering again, showing the error " Warning: Encountered two children with the same key, AhO8HUMnDjAH1Mh8u2jM. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version."
Here is my code
PLs note I have used pagination, and infinite scroll list methods.
import React,{useState,useEffect} from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, Button } from 'react-native';
import firestore from '#react-native-firebase/firestore';
import { Icon } from "react-native-vector-icons/Ionicons";
const HomeScreen = ({navigation}) => {
const [sessions,setSessions] = useState(new Array());
const [sessionsPerLoad] = useState(12)
const [startAfter,setStartAfter] = useState(Object)
const [lastPost,setLastPost] = useState(false)
//read docs
const getSessions = async (sessionsPerLoad) => {
const sessionArray = [];
const querySnap = await firestore()
.collection('sessions')
.orderBy('createdAt', 'desc')
.limit(sessionsPerLoad)
.get()
const lastVisible = querySnap.docs[querySnap.docs.length - 1]
querySnap.forEach((doc)=> {
let sessionData = doc.data()
sessionData.sessionID = doc.id
sessionArray.push(sessionData)
})
return {sessionArray, lastVisible}
}
const getMoreSessions = async (startAfter,sessionsPerLoad) => {
const sessionArray = [];
const querySnap = await firestore()
.collection('sessions')
.orderBy('createdAt', 'desc')
.startAfter(startAfter)
.limit(sessionsPerLoad)
.get()
const lastVisible = querySnap.docs[querySnap.docs.length - 1]
querySnap.forEach((doc)=> {
let sessionData = doc.data()
sessionData.sessionID = doc.id
sessionArray.push(sessionData)
})
return {sessionArray, lastVisible}
}
useEffect(()=>{
getSession()
},[])
const getSession = async () => {
const sessionsData = await getSessions(sessionsPerLoad)
setSessions([...sessions, ...sessionsData.sessionArray])
// console.log('Sessions',sessions)
setStartAfter(sessionsData.lastVisible)
// console.log('Last VIsible',sessionsData.lastVisible)
}
const getMoreSession = async () => {
if(!lastPost){
const sessionsData = await getMoreSessions(startAfter,sessionsPerLoad)
setSessions([...sessions, ...sessionsData.sessionArray])
// console.log('More Session',sessions)
setStartAfter(sessionsData.lastVisible)
sessionsData.sessionArray.length==0 ? setLastPost(true):setLastPost(false)
}
}
const RenderCard = ({item})=>{
return(
<TouchableOpacity onPress={()=>{navigation.navigate('HomeScreen2', {item})}}>
<View style={{padding: 10}}>
<Text>Title= {item.title}</Text>
<Text>Description= {item.description}</Text>
</View>
</TouchableOpacity>
)
}
return(
<View>
<FlatList
data={sessions}
renderItem={({item})=><RenderCard item={item} />}
keyExtractor={(item)=>item.sessionID}
onEndReached={getMoreSession}
onEndReachedThreshold={0.01}
scrollEventThrottle={150}
ListFooterComponent={()=>
!lastPost && <ActivityIndicator />}
/>
</View>
);
};
const styles = StyleSheet.create({
});
export default HomeScreen
Pls ignore this issue, in the development phase, this will happen.
To ignore these warnings use -> https://stackoverflow.com/a/67670955/13733008
I am using the Free Meal API with flatlist. I have Category component, Categories page, useFetch hook. I can't see Flatlist on screen. I can get console log of data but I can't reach datas with flatlist.
.env folder:
API_URL_CATEGORIES="https://www.themealdb.com/api/json/v1/1/categories.php"
API_URL_FILTER="https://www.themealdb.com/api/json/v1/1/filter.php?"
useFetch hook for getting the data in URL and returning Loading icon, Error if URL doesn't work and data for data in URL.
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const fetchData = async () => {
try {
const {data: responseData} = await axios.get(url);
setData(responseData);
setLoading(false); }
catch (error) {
setError(error.message);
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {error, loading, data};
};
export default useFetch;
Category component:
const Category= ({category, onSelect}) => {
return(
<TouchableOpacity style={styles.container} onPress={onSelect}>
<Image
style={styles.image}
source={{uri:category.strCategoryThumb}} />
<Text style={styles.title}>{category.strCategory}</Text>
</TouchableOpacity>
)
}
export default Category;
Categories page:
const Categories = ({navigation}) => {
const { error, loading, data } = useFetch(config.API_URL_CATEGORIES);
console.log(data)
const handleCategorySelect = strCategory => {
navigation.navigate("Detail", {strCategory})
}
const renderCategory = ({item}) => <Category category={item} onSelect={() => handleCategorySelect(item.strCategory)}/>;
if(loading) {
return <Loading/>;
}
if(error) {
return <Error/>;
}
return(
<View style={styles.container}>
<FlatList data={data} renderItem={renderCategory}/>
<Text>Categorises</Text>
</View>
)
}
export default Categories;
I think data is actually object that contains a property categories , which holds an array.
try data.categories and I believe this should work fine.
Here is my code:
import React, {useState} from 'react';
import {useQuery, useInfiniteQuery} from 'react-query';
import {getMeetup} from '../../api/methods/getMeetups';
export default function MyFunction(props) {
const [next, setNext] = useState('');
const fetchData = async ({pageParam = ''}) => {
const response = await getMeetup( pageParam);
console.log('API RESP', response);
return response;
};
const {data, isLoading, fetchNextPage} = useInfiniteQuery(
'myData',
fetchData,
{
getNextPageParam: (lastPage, pages) => lastPage?.next?._id,
},
);
console.log('RQUERY CHECK', data);
const getMore = () => {
console.log('data end', data?.pages[0]?.next?._id);
fetchNextPage({pageParam: data?.pages[0]?.next?._id});
};
const flattenData = data?.pages
? data?.pages?.flatMap((page) => [...page.Docs])
: [];
return (
<View>
<FlatList
style={{
marginBottom: verticalScale(40),
paddingHorizontal: scale(15),
}}
data={flattenData}
keyExtractor={(item) => item._id}
renderItem={({item, index}) => {
return <ListItem data={item} index={index} />;
}}
onEndReachedThreshold={0.1}
onEndReached={getMore}
/>
</View>
);
}
The problem i am facing is when the page loads the api calls one by one with unique next ids or page param. What i was trying to implement is , when user reaches the end of the page (onEndReached) the data needs to be fetched with a new page param.
getNextPageParam return the value for the next page. So you don't need pass pageParam in fetchNextPage unless you want overwrite, for any reason, the next page value.
You can add a hasNextPage validation for unnecessary requests.
const {data, isLoading, hasNextPage, fetchNextPage} = useInfiniteQuery( /* your code */ )
const getMore = () => {
if(hasNextPage)
fetchNextPage();
};
I have a function from where I want to return JSX. I´m new to React, I tried this:
history is array of objects and I want to return text from it and display it.
renderCard = () => {
const db = firebase.firestore();
db.collection("users")
.doc(firebase.auth().currentUser.uid)
.collection("userRequestDeposit")
.get()
.then(snapshot => {
let history = snapshot.docs.map(doc => {
const data = doc.data().request;
return { ...data };
});
history.forEach(elem => {
return (
<View>
<Text>{elem.data}</Text>
</View>
);
});
});
};
So this is a nice case study for how to use React.
You want to fetch this data when the component mounts. When you have the data, you will update the component's state. You can then render the output from that state.
Here is how you could do this with your code:
import {useEffect, useState} from 'react';
const YourComponent = () => {
const [history, setHistory] = useState([]); // Store your data in this state
// this useEffect will run only once (when the component mounts).
useEffect(() => {
db.collection('users')
.doc(firebase.auth().currentUser.uid)
.collection('userRequestDeposit')
.get()
.then(snapshot => setHistory(snapshot.docs.map(doc => ({...doc.data().request}))));
}, []); // <-- the empty dependency array means it only runs on mount
// when history is updated, you can map over the array and render the output here
return history.map(item => (
<View key={item.id}>
<Text>{item.data}</Text>
</View>
));
};
or as a class component...
import {Component} from 'react';
class YourComponent extends Component {
state = {
history: [],
};
componentDidMount() {
db.collection('users')
.doc(firebase.auth().currentUser.uid)
.collection('userRequestDeposit')
.get()
.then(snapshot => {
this.setState({history: snapshot.docs.map(doc => ({...doc.data().request}))});
});
}
render() {
return history.map(item => (
<View key={item.id}>
<Text>{item.data}</Text>
</View>
));
}
}
I am trying to render the API data onto the page but get this error
TypeError: undefined is not a function (near "...dataSource.map...")
Unrelated Question: Also I am new to mobile app development and am wondering when you get data from an API is best practice to store it in a database or render it directly onto a page?
import React, { Component, useState, useEffect } from "react";
import { View, Text, StyleSheet, ActivityIndicator } from "react-native";
import { ScreenContainer } from "react-native-screens";
export const Home = () => {
const [isLoading, setisLoading] = useState(true);
const [dataSource, setdataSource] = useState(null);
useEffect(async () => {
const response = await fetch(
"https://facebook.github.io/react-native/movies.json"
);
const data = await response.json();
const movies = data.title;
setdataSource(movies);
setisLoading(false);
// .catch((error)) => {
// console.log(error)
// }
}, []);
if (isLoading) {
return (
<View>
<ActivityIndicator />
</View>
);
} else {
let moviest = dataSource.map((val, key) => {
return (
<View key={key}>
<Text>{val}</Text>
</View>
);
});
return (
<ScreenContainer style={styles.container}>
<View>{moviest}</View>
</ScreenContainer>
);
}
You have to check dataSource is null or undefined before map.
let moviest = dataSource && dataSource.map((val, key) => {
Please check type of dataSource.
When I check https://facebook.github.io/react-native/movies.json data, type of data.title is string("The Basics - Networking")
Maybe you should use movies.
const data = await response.json();
const movies = data.movies;
setdataSource(movies);
setisLoading(false);