I'm trying to fetch data from firebase.
I want to push the values in an array and map through it in a view.
The only problem is that i can see the values in my array but not outside the .then function.
const dataArray = []
firebase
.database()
.ref("Challenges/Day")
.once("value")
.then((snapshot) => {
snapshot.forEach((child) => {
dataArray.push(child.val());
});
console.log(dataArray)
});
return (
<View style={styles.text}>
{dataArray.map((data) => {
<Text>{data}</Text>;
})}
</View>
);
if i console log it then the output is :
Array [
"eat",
"sleep",
]
But outside it's function my array is empty.
The issue here is console.log() is getting executed even before the data is returned from the firebase as response. When the request is made it takes sometime (say 1 sec) to return the response, but in the meantime the control executes the next line which is console.log(). When this line is executed, the value of dataArray=[] at that amoment. So for your case it is best to keep dataArray as state variable, so when the response is returned you can set the value of the dataArray which will re-render your component, thus displaying its return value.
const [dataArray,setDataArray] = useState([])
firebase
.database()
.ref("Challenges/Day")
.once("value")
.then((snapshot) => {
let tempArray=[];
snapshot.forEach((child) => {
tempArray.push(child.val());
}
setDataArray(tempArray);
);
console.log(dataArray)
});
return (
<View style={styles.text}>
{dataArray.map((data) => {
<Text>{data}</Text>;
})}
</View>
);
It's due to 2 issues you are facing.
asynchrounous - means what you are seeing in UI is the [] before firebase is executed.
state update - there were no state updated, hence even after the variable change occurred in firebase, the UI is not updated. Any normal variable changes will not re-render the UI, only a *state change will trigger a re-render in React.
What you can do.
const [dataArray, setDataArray] = useState([])
useEffect(() => {
firebase
.database()
.ref("Challenges/Day")
.once("value")
.then((snapshot) => {
snapshot.forEach((child) => {
//call an update to the array.
//setDataArray((oldDataArray) => oldDataArray.push(child.val()))
setDataArray((oldDataArray) => [...oldDataArray, child.val()])
});
console.log(dataArray)
});
}, []); //will only initialize once(on mount). Putting outside will get called again and again when state change.
return (
<View style={styles.text}>
{dataArray.map((data) => {
<Text>{data}</Text>;
})}
</View>
);
Related
I have created an API to access data from the database. I have fetched the api data using axios and I want to display it. I have mapped the data, but it returns empty array for the first time.
const ExpenseList = () => {
const [result, setResult] = useState([]);
const expenseDetails = async () => {
try {
let res = await axios.get("http://127.0.0.1:8000/expense");
let result = res.data;
setResult(result);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
expenseDetails()
}, []);
console.log("result", result)
return (
<Container className='list-group'>
<Row className='title-row'>
<Col className='title-col-expenses'>Expenses</Col>
</Row>
{
result.map((items)=>{
<ExpenseItem id={items.id} name={items.name} cost={items.cost} />
})}
</Container>
)
}
I have attached a screenshot of console where I have logged "result"
Click to view image
as CevaComic said you are setting the initial value as an empty array.
useEffect will only work after the component has been rendered, so when you will console.log the data stored in result you will get the initial value.
Only after the component will render for the second time, because of the changed made inside setResult, the data from the api will be logged.
Exactly as the title says I am fetching data from a database and trying to display the stored image URI as an image in my React Native application. I'm not entirely sure where the process is going wrong, but my array is being filled and nothing is being shown. I have tried hardcoding the data that's being fetched into my application and it is being displayed then. I have wrapped my view statement that the flatlist is nested into with the following: {arrayName && .... }. Here is some relevant code. The Test field appears, but the flatlist will not.
const [data, setData] = React.useState([]);
const userImgData = [];
{userImgData && <View style={{width: 300, height: 300}}>
<FlatList
data={userImgData}
keyExtractor={(item) => item.id}
key={item=> item.id}
renderItem={({ item }) => (
//<Text style={styles.headerText}>{item.id}</Text>
<Image source={{ uri: item.imageURI}} style={{ width: 200, height: 200 }} />
)}
/>
<Text>Test</Text>
</View>}
My array is set here...
function getData(){
axios.get('IP/imagefiles')
.then((response) => {
const myObjects = response.data;
setData(myObjects);
});
//console.log(data);
for(let i = 0; i < data.length; i++){
if(data[i].user == user){
userImgData.push(data[i]);
}else{
console.log('no data found!');
};
};
console.log(userImgData);
};
Here is how the data is coming across...
Edit: Changed userImgData to a state variable and got some results to show.
There appears to be a couple of issues present...
You're trying to loop over data before it is assigned a value. This is because the Axios request is asynchronous so the code after the request runs before the code in the then() callback.
userImgData isn't a state variable and even if it was, using push() won't trigger a state update and won't re-render your component.
Try using a memo hook to provide the filtered userImgData instead
const [data, setData] = useState([]);
const userImgData = useMemo(
() => data.filter((d) => d.user == user),
[data, user]
);
const getData = async () => {
setData((await axios.get("IP/imagefiles")).data);
};
You can check userImgData.length to conditionally render the list.
I'm building an app with React and Firestore.
In one feature, I need to use some specific user data to query data from another collection, and show that on the app.
Specifically I want to use users.books, which returns an array, to query the collection books.
However, for some reason the users.books doesn't load on first render. It typically takes 2-3 renders to fetch the books.user data. This is despite the currentUserUID being loaded right away.
I've tried using a loading state as specified in How to wait for Firebase data to be fetched before progressing?, but to no avail.
Do I need to use the onSnapShot method?
Thanks for reading
My code
import 'firebase/firestore'
import { booksRef} from '../../App';
const ProfileScreen = ({ navigation }) => {
const currentUserUID = firebase.auth().currentUser.uid;
const [firstName, setFirstName] = useState('');
const [userBookTitles, setUserBookTitles] = useState([]);
const [userBooks, setUserBooks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function getUserInfo(){
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
setFirstName(dataObj.firstName)
setUserBookTitles(dataObj.books)
console.log(userBookTitles)
}
}
getUserInfo();
}, [])
useEffect(() => {
async function getUserBooks() {
booksRef.where("title", "in", userBookTitles).onSnapshot(snapshot => (
setUserBooks(snapshot.docs.map((doc) => ({id: doc.id, ...doc.data()})))
))
}
setLoading(false);
getUserBooks()
}, [])
if (!loading) {
return (
<View style={styles.container}>
<Text> Hi {firstName} </Text>
<TouchableOpacity onPress={handlePress}>
<Text> Log out </Text>
</TouchableOpacity>
<Row
books={userBooks}
/>
</View>
);
} else {
return (
<View style={styles.container}>
<Text> Test </Text>
</View>
);
}
};
So it's worth noting that your setX methods may, or may not, complete in the sequence you have them in your code. Therefore, your booksRef call could be being made even though userBookTitles is an empty array. Which would explain why you're not getting any data on load.
You're setting userBookTitles in your first useEffect and the only other place I see you're using it is in your booksRef call. One easy fix would be to simple move booksRef inside the else statement of the first useEffect and simply pass it the userBookTitles there. This should help in solving your issue, if I understood it correctly.
I'm calling an Api to get data but the data is really heavy. I'm wondering if i'm calling it in right place inside useEffect or should i call it somewhere else. I've put the console.log to check but the number of console.log exceeded the number of objects i have in the API. My code is :
const ProductsList = () => {
const [products, setProducts] = useState([]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
getProducts().then((response) => {
if (isMounted) {
console.log('im being called');
setProducts(response);
setLoading(false);
}
});
return () => { isMounted = false; };
}, [products]);
return (
<View style={styles.container}>
{isLoading ? <ActivityIndicator /> : ((products !== [])
&& (
<FlatList
data={products}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<Item
style={{ marginLeft: 35 }}
name={item.name}
date={item.date}
address={item.adress}
/>
);
}}
/>
)
)}
</View>
);
};
It looks like your effect goes round in a circle:
On each render the effect will look at products to see if it has changed.
If it has changed it will call your effect which fetches new products.
When you get new products you update your products state.
This causes the effect to run again.
You probably only want to run that effect once, when the component mounts. In which case you can simply write:
useEffect(() => {
getProducts().then((response) => {
setProducts(response);
setLoading(false);
});
}, []); // <-- note the empty array, which means it only runs once when mounted
If that single API call is too heavy, then you need to look more at things like pagination in your requests, or modifying the response to return only the data you really need. But that's outside the scope of this question, I think.
Let me know if you have any questions.
I am using useEffect to fetch array data then display items. When I fetch data I could see that the data is fetched successfully.
But when I try to display data in the view it gives me an error Cannot read property 'name' of undefined
I think the view is rendering before the fetch. But I used async await to make sure the useEffect is called first.
Any idea of how to get the data display in the view?
Any comments or advice would be really helpful. Thanks in advance :)
const FetchUsers = () => {
const [data, setData] = useState([])
const [loading, setLoading] = useState([])
const URL = "myurl..."
useEffect(() => {
async function fetchData() {
const res = await axios.get(URL, {headers: {"x-access-token":memberToken}});
const response = await res;
console.log(response.data.data,'data')
//when I console log
// [{…}, {…}] "data"
//0: { name: "kevin", age: 39}
//1: { name: "john", age: 23}
console.log(response.data.data[0].title, 'name')
setData(response.data.data);
setLoading(false);
}
fetchData()
}, [])
return(
<View>
//I am trying to display fetched data
<Text>{data[0].name}</Text>
</View>
)
}
There is no "called first". Your component renders immediately, useEffect contains a "side-effect" - something that happens at some unknown point after your component renders....
Because of this, data will be set at some point in the future, so you have to check if it's available first, if it's not, then render something in it's place, like:
return(
<View>
<Text>{!data.length ? 'Loading...' : data[0].name}</Text>
</View>
)
useEffect() will only trigger once the component is finished rendering and when the state or props changes. In your case, you are displaying the name attribute at initial render. This is the lifecycle, render > useEffect > props/state change? > useEffect > render.
use this instead:
return (
<View>
<Text>{data.length > 0 ? data[0].name : 'Fetching...'}</Text>
</View>
);