Fetch Inside A Fetch - Objects Are Not Valid As A React Child - javascript

I am trying to make two fetch requests. The second one cannot be completed until MemberId is retrieved from the first one. I have tried putting them in separate async functions but keep getting the below error.
Any insights on the best way to do this would be much appreciated.
Error message:
Uncaught Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
import { StatusBar } from "expo-status-bar";
import { useEffect, useState } from "react";
import { StyleSheet, Image, Text, View } from "react-native";
export default async function App() {
const [isLoading, setLoading] = useState(true);
const [isLoadingMp, setLoadingMp] = useState(true);
const [data, setData] = useState([]);
const [mpData, setMpData] = useState([]);
let memberId = 0;
useEffect(() => {
fetch(
`https://members-api.parliament.uk/api/Members/Search?Name=Boris%20Johnson`
)
.then((response) => response.json())
.then((json) => {
setMpData(json);
memberId = json.items[0].value.id;
console.log("memberId:", memberId);
})
.catch((error) => console.error(error))
.then(
fetch(
`https://commonsvotes-api.parliament.uk/data/divisions.json/membervoting?memberId=${memberId}`
)
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false))
.finally(() => {
setLoadingMp(false);
})
);
});
return (
<View style={{ flex: 1, padding: 24 }}>
{isLoading || isLoadingMp ? (
<Text>Loading...</Text>
) : (
<View style={styles.container}>
<Text>
<Image
source={{
uri: `${mpData.items[0].value.thumbnailUrl}`,
width: 60,
height: 60,
}}
/>
<Text>{`${mpData.items[0].value.id}\n`}</Text>
{data.map((individualData) => {
return `\nDate: ${
individualData.PublishedDivision.Date
}\nDivision id: ${
individualData.PublishedDivision.Date
}\nDivision title: ${
individualData.PublishedDivision.Title
}\nVoted: ${!!individualData.MemberVotedAye ? "Yes" : "No"}\n`;
})}
</Text>
<StatusBar style="auto" />
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});

import { StatusBar } from "expo-status-bar";
import { useEffect, useState } from "react";
import { StyleSheet, Image, Text, View } from "react-native";
export default function App() {
const [isLoading, setLoading] = useState(true);
const [isLoadingMp, setLoadingMp] = useState(true);
const [data, setData] = useState([]);
const [mpData, setMpData] = useState([]);
let memberId = 0;
useEffect(() => {
const callApi = async ()=>{//defining a async function to call the API
setLoading(true);//maybe you just need one loading flag
try{
const result = await (await fetch(
`https://members-api.parliament.uk/api/Members/Search?Name=Boris%20Johnson`
)).json();//first api and json() its result
const memberId = result.items[0].value.id;//get the member id from it
const finalResult = await(await fetch(
`https://commonsvotes-api.parliament.uk/data/divisions.json/membervoting?memberId=${memberId}`
)).json();//second api call and json() its result
//set the state
setData(finalResult);
setMpData(result);
}catch(e){
console.log(e)
}finally{
setLoading(false);//set loading false in both the fail and success case
}
}
callApi();//calling the function
}, []);
//VIEW rendering
return (<View style={{ flex: 1, padding: 24 }}>
{isLoading ? (
<Text>Loading...</Text>
) : (
<View style={styles.container}>
<Text>
<Image
source={{
uri: `${mpData.items[0].value.thumbnailUrl}`,
width: 60,
height: 60,
}}
/>
<Text>{`${mpData.items[0].value.id}\n`}</Text>
{data.map((individualData) => {
return `\nDate: ${
individualData.PublishedDivision.Date
}\nDivision id: ${
individualData.PublishedDivision.Date
}\nDivision title: ${
individualData.PublishedDivision.Title
}\nVoted: ? "Yes" : "No"}\n`;
})}
</Text>
<StatusBar style="auto" />
</View>
)}
</View>);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});

This specific problem has nothing to do with the fetches, it is simpler.
You declared your functional component as async:
export default async function App() {
The consequence is that it doesn't return a React element but a Promise that resolves to a React element, which React can't handle.
Remove the async:
export default function App() {

Related

why not show text when calling api in react native

when calling api returns only red background but not text
I expect the api to return a list of movies displayed on the app interface
but when i return the Items function directly inside the render function, it returns me the text, but when I call the function outside, it just doesn't return the text but just the background
import React, {useEffect, useState} from 'react';
import {ActivityIndicator, FlatList, Text, View} from 'react-native';
import Axios from 'axios';
export default App = () => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
Axios.get('https://reactnative.dev/movies.json')
.then(({data}) => {
setData(data.movies);
})
.catch(error => console.log('error', error))
.finally(() => setLoading(false));
}, []);
const Items = item => {
return (
<View>
<Text style={{flex: 1, backgroundColor: 'red', color: 'blue'}}>
{item.title}, {item.releaseYear}
</Text>
</View>
);
};
return (
<View style={{flex: 1, padding: 24}}>
{isLoading ? (
<ActivityIndicator />
) : (
<FlatList
data={data}
keyExtractor={(item, index) => {
return index.toString();
}}`enter code here`
renderItem={Items}
/>
)}
</View>
);
};
Change your Items component likewise :
const Items = ({item}) => {
return (
<View>
<Text style={{flex: 1, backgroundColor: 'red', color: 'blue'}}>
{item.title}, {item.releaseYear}
</Text>
</View>
);
};

"Error: Invalid hook call. Hooks can only be called inside of the body of a function component.." in react native expo

TestScreen.js
export default function TestScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<AlcoResult/>
</View>
);
}
2.AlcoResult.js
import { StyleSheet, Text, View,TouchableOpacity } from 'react-native'
import React from 'react'
import AlcoTestButton from './AlcoTestButton'
const AlcoResult = () => {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={()=>AlcoTestButton()}
style={styles.button}>
<Text style={{ color: "white"}}>pull data</Text>
</TouchableOpacity>
</View>
)
}
AlcoTestButton.js
import { StyleSheet, Text, View, ActivityIndicator, FlatList } from 'react-native'
import React, { useEffect, useState, Component } from 'react'
import { SafeAreaView } from 'react-navigation';
const url = "url";
const AlcoTestButton = () => {
const [isLoading,setLoading] = useState(true);
const [alcohol, setAlcohol] = useState([]);
const [temperature, setTemperature] = useState([]);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((json) => {
setAlcohol(json.alcohol);
setTemperature(json.temperature);
})
.catch((error) =>alert(error))
.finally(setLoading(false));
})
return (
<SafeAreaView style={styles.container}>
{isLoading ? (<ActivityIndicator />) :(
<View>
<Text>Alcohol = {alcohol}</Text>
<Text>Temperature = {temperature}</Text>
</View>
)}
</SafeAreaView>
)
}
export default AlcoTestButton
So here is my code... I tried different solutions on several websites but still got the same error.
I'm new to react native, If possible could anyone point out what are my errors if any in the structure of the codes?
Thank you.
The problem is that you are calling a component and returning UI elements instead of a function when pressing the button, so i'd suggest something like this
(I'm unsure of the structure of your data, but this should put you on the right track at least)
const AlcoResult = () => {
const [isLoading,setLoading] = useState(false);
const [alcohol, setAlcohol] = useState();
const [temperature, setTemperature] = useState();
const fetchData= async ()=>{
setLoading(true)
fetch(url)
.then((response) => response.json())
.then((json) => {
setAlcohol(json.alcohol);
setTemperature(json.temperature);
})
.catch((error) =>{alert(error)})
.finally(()=>{setLoading(false)});
}
return (
<View style={styles.container}>
<TouchableOpacity
onPress={()=>fetchData()}
style={styles.button}>
<Text style={{ color: "white"}}>pull data</Text>
</TouchableOpacity>
{isLoading && (<ActivityIndicator />)}
{!isLoading && !!temperature && !!alcohol &&
<View>
<Text>Alcohol = {alcohol}</Text>
<Text>Temperature = {temperature}</Text>
</View>
}
</View>
)
}

React Component Not Re Rendering using Spread Operator

I've tried other solutions on this site, but I am not sure why this is not working. I have a response I am getting from a server each time a button is pressed. The response comes through fine and I am able to see it each time the button is pressed. I am receiving an array of objects from the server, and I am using the useState hook to set the state of a variable to keep track of the objects in the array. However, the component does not re render. Interestingly, if I add a console.log statement to see the contents of the state variable and then save the page, I can see that the state variable was updated properly. The component still does not re render though. The relevant code is here:
import React, { useEffect, useState } from "react";
import { StyleSheet, View, TouchableOpacity, Alert } from "react-native";
import tailwind from "tailwind-rn";
import colors from "../config/colors";
import useAuth from "../hooks/useAuth";
import Screen from "../components/Screen";
import AppText from "../components/AppText";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import { getData } from "../hooks/useCache";
import { useIsFocused } from "#react-navigation/native";
import ListScreen from "./ListScreen";
const ProfileScreen = () => {
const { logout, user } = useAuth();
const [likes, setLikes] = useState("");
const [completed, setCompleted] = useState("");
const [responseJson, setResponseJson] = useState(null);
const isFocused = useIsFocused();
useEffect(() => {
const likesFunc = async() => {
setLikes(await getData("likes"));
setCompleted(await getData("eventsCompleted"));
try {
const response = await fetch(
"server url here",
{
headers: { Authorization: "Bearer " + user.idToken },
}
);
const responseJson = await response.json();
setLikes(responseJson.likes);
setCompleted(responseJson.eventsCompleted);
} catch {
Alert.alert("There has been an error processing your profile");
}
}
likesFunc();
}, []);
//get voted events
//run when questionsLeft is 0 to save num of calls
useEffect(() => {
const eventFunction = async() => {
try {
const response = await fetch(
"server url here",
{
headers: { Authorization: "Bearer " + user.idToken },
}
)
const res = await response.json();
setResponseJson([...res]);
} catch (error) {
Alert.alert(
"An error has occurred loading your questions. Close the app and try again."
);
console.log(error);
}
}
eventFunction();
}, [isFocused]);
return (
<Screen style={styles.bg}>
<View
style={[
tailwind("w-full flex-row py-4 justify-center items-center top-0"),
{ justifyContent: "space-between" },
]}
>
<AppText style={{ color: colors.white, fontSize: 30, marginLeft: 5 }}>
Hello, {user.displayName.split(" ")[0]}
</AppText>
<TouchableOpacity
style={styles.logoutButton}
onPress={() => {
Alert.alert("Log Out", "Are you sure you want to log out?", [
{
text: "Yes",
style: "destructive",
onPress: logout,
},
{
text: "Cancel",
style: "cancel",
},
]);
}}
>
<MaterialCommunityIcons
name="logout-variant"
size={25}
color={colors.primary}
/>
</TouchableOpacity>
</View>
<View
style={tailwind("w-full h-1/5 justify-center items-center")}
>
<View
style={[
tailwind("w-full flex-row p-10 justify-center"),
{ justifyContent: "space-between" },
]}
>
<View style={tailwind("justify-center items-center")}>
<AppText style={{ textDecorationLine: "underline" }}>
Total Likes
</AppText>
<AppText style={{ paddingVertical: 10 }}>{likes}</AppText>
</View>
<View style={tailwind("justify-center items-center")}>
<AppText style={{ textDecorationLine: "underline" }}>
Completed
</AppText>
<AppText style={{ paddingVertical: 10 }}>{completed}</AppText>
</View>
</View>
</View>
<View
style={tailwind("w-full h-4/5 flex-1 items-center")}
>
{responseJson == null ?
<AppText style={tailwind("mt-10")}>
Select events on the "Discover" page!
</AppText>
:
<ListScreen caller={{"sender": "profile", "json": responseJson}}/>
}
</View>
</Screen>
);
};
export default ProfileScreen;
const styles = StyleSheet.create({
logoutButton: {
color: colors.white,
paddingTop: 20,
paddingRight: 10,
},
bg: {
flex: 1,
backgroundColor: colors.black,
},
});
Update: If I set the state twice in a row, it works:
setResponseJson(null);
setResponseJson([...res]);
However, this is somewhat buggy and not optimal. Leads me to think it still is a reference issue, but I am not sure why the spread operator technique does not fix this.
Three things are wrong/weird in your code. Not sure it will fix your problem but here are they :
1- Do not pass an async function to the useEffect. Instead, create an async function inside and call it :
useEffect(() => {
const myFunc = async => {...};
myFunc();
},[]);
2- You are receiving an array of objects from your api call, so why do you want to spread it ? It would lead to multiple objects inside your state, which doesn't seems right. Just pass your res in your state as is or format it as you want.
3- You are mixing async/await pattern with the .then. Either use an async func and await the result :
const myFunc = async () => {
const resPromise = await fetch(...);
const json = await resPromise.json();
...
}
or only use the .then:
const myFunc = () => {
fetch(...)
.then(data => data.json())
.then(json => ...);
...
}

useState is not working properly in React Native

I'm working on an application and using WordPress API for showing posts. I've created 2 buttons to navigate the list of posts. As you know there is an argument "page=" to get posts on a specific page, I've initialized a state to maintain page number. The main problem is that it's not incrementing correctly.
Post Screen Code -
import React, { useState, useEffect } from "react";
import { View, FlatList, TouchableOpacity } from "react-native";
import { Colors } from "../constant/colors";
import globalStyles from "../constant/globalStyle";
import axios from "axios";
import PostCard from "../components/PostCard";
import CustomButton from "../components/Button";
const Updates = () => {
const [data, setData] = useState([]);
const [page, setPage] = useState(1);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await axios.get(
`https://bachrasouthpanchayat.in/wp-json/wp/v2/posts?embed=true&page=${page}`
);
setData(response.data);
setLoaded(true);
};
const previousHandler = () => {
setLoaded(false);
let newPage = page - 1;
setPage(newPage);
fetchData();
};
const nextHandler = () => {
setLoaded(false);
let newPage = page + 1;
setPage(newPage);
fetchData();
};
return (
<View
style={{
...globalStyles.container,
backgroundColor: Colors.BACKGROUND_SCREEN,
}}
>
{loaded ? (
<>
<FlatList
style={{ flex: 1, margin: 10 }}
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return (
<TouchableOpacity activeOpacity={0.7}>
<PostCard
title={item.title.rendered}
imageUrl={item.jetpack_featured_media_url}
/>
</TouchableOpacity>
);
}}
/>
<View
style={{
flexDirection: "row",
alignItems: "center",
alignContent: "stretch",
justifyContent: "center",
}}
>
{page == 1 ? (
<TouchableOpacity
activeOpacity={0.7}
style={{ width: "100%" }}
onPress={nextHandler}
>
<CustomButton>Next</CustomButton>
</TouchableOpacity>
) : (
<>
<TouchableOpacity
activeOpacity={0.7}
style={{ marginRight: 2, width: "50%" }}
onPress={previousHandler}
>
<CustomButton>Previous</CustomButton>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
style={{ width: "50%" }}
onPress={nextHandler}
>
<CustomButton>Next</CustomButton>
</TouchableOpacity>
</>
)}
</View>
</>
) : null}
</View>
);
};
export default Updates;
I had logged state in every state and found that was not incrementing from 1 to 2 on pressing the button the first time. I think state updated after API call because both buttons had started showing even I've used condition to show both buttons only if the state is not 1
Please let me know if i've made any silly mistake 😂😂
useEffect(() => {
fetchData();
}, []);
The last argument to useEffect is an array of dependencies so that React will only re-run the effect when the dependencies have changed. You are passing an empty array, which tells React that there are no dependencies and the effect should only be run once, when the component is first mounted.
Now you actually want the effect to re-run when the page changes, so you should put page in the depenency array:
useEffect(() => {
fetchData();
}, [page]);
And (credit: #Keith) you should remove the extra fetchData() calls in the nextHandler and previousHandler
You have to implement it like this:
useEffect(() => {
const fetchData = async () => {
setLoaded(true);
const response = await axios.get(
`https://bachrasouthpanchayat.in/wp-json/wp/v2/posts?embed=true&page=${page}`
);
setData(response.data);
setLoaded(false);
};
fetchData();
}, [page]);
const previousHandler = () => {
setPage(prevPage => prevPage - 1);
};
This way whenever the user changes the page, it will automatically call the function in useEffect, since it is in the dependency array.

How to wait for Firebase data to be fetched before progressing?

I am fetching data from Fire store in real-time with .onSnapshot and it works great, I am receiving the data as expected. The problem is that I am receiving multiple sets of data, and the component does not wait until all the data is received before rendering.
So my question is, with my current code, is their a way in which I can wait for all sets of my data to be fetched before displaying them?
My current code is:
import React, {useEffect, useState} from 'react';
import {ActivityIndicator, Dimensions, Text, View} from 'react-native';
import firestore from '#react-native-firebase/firestore';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import FolloweringScreens from './FolloweringScreens';
import {TouchableOpacity} from 'react-native-gesture-handler';
const {width, height} = Dimensions.get('screen');
function Following({urlname, navigation}) {
const [followingData, setfollowingData] = useState([]);
// Follower counts, displayname, image
const fetchData = () => {
const dataRef = firestore().collection('usernames');
dataRef
.doc(urlname)
.collection('Following')
.onSnapshot((snapshot) => {
snapshot.forEach((doc) => {
dataRef.doc(doc.id.toLowerCase()).onSnapshot((followerDoc) => {
const data = followerDoc.data();
setfollowingData((prev) => [
...prev,
{
profileName: doc.id,
displayName: data.userName,
followerCount:
data.followers !== undefined ? data.followers : 0,
followingCount:
data.following !== undefined ? data.following : 0,
image: data.imageUrl ? data.imageUrl : null,
},
]);
});
});
});
};
useEffect(() => {
fetchData();
}, []);
return (
<>
<View
style={{
left: width * 0.04,
top: 50,
flexDirection: 'row',
alignItems: 'center',
width: '80%',
height: '4%',
marginBottom: 5,
}}>
{/* {console.log('followin', followingData)} */}
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon name="menu" color="#222" size={30} />
</TouchableOpacity>
<Text style={{left: width * 0.05}}>Following</Text>
</View>
{followingData === [] ? (
<ActivityIndicator size="large" color="black" />
) : (
<>
<FolloweringScreens data={followingData} />
</>
)}
</>
);
}
export default Following;
Use a state isLoading default true, then set isLoading to false once a snapshot resolves, and show a loading indicator on isLoading true and show your ui when isLoading false. Then you get updates pushed to your state and the user will see the data once it's fully loaded.
Would also use something close to this. One thing that is quite weird is that you push every snapshot change to an array, so in other words over time this array holds a history of changes of the same object. Intentional?
function Following({ urlname }) {
const [followingData, setfollowingData] = useState([]);
const [isLoading, setIsLoading] = useState(true);
// Follower counts, displayname, image
const onSnapshot = useCallback((snapshot) => {
snapshot.forEach((doc) => {
dataRef.doc(doc.id.toLowerCase()).onSnapshot((followerDoc) => {
const data = followerDoc.data();
// push new document data into an array
setfollowingData((prev) => [
...prev,
{
profileName: doc.id,
displayName: data.userName,
followerCount: data.followers !== undefined ? data.followers : 0,
followingCount: data.following !== undefined ? data.following : 0,
image: data.imageUrl ? data.imageUrl : null
}
]);
// or set the new data to state, by just setting the document data
setfollowingData(data);
setIsLoading(false);
});
});
}, []);
useEffect(() => {
const dataRef = firestore().collection("usernames");
const cleanup = dataRef
.doc(urlname)
.collection("Following")
.onSnapshot(onSnapshot);
return cleanup;
}, [onSnapshot, urlname]);
return (
<>
{isLoading && <p>Loading</p>}
{!isLoading && <p>Show data {followingData.length}</p>}
</>
);
}
So I managed to fix it somehow. Thanks to Julian for the help
What I did was create an array of promises which will be executed whenever the data changes. The code is:
import React, {useCallback, useEffect, useState} from 'react';
import {ActivityIndicator, Dimensions, Text, View} from 'react-native';
import firestore from '#react-native-firebase/firestore';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import FolloweringScreens from './FolloweringScreens';
import {TouchableOpacity} from 'react-native-gesture-handler';
const {width, height} = Dimensions.get('screen');
function Following({urlname, navigation}) {
const [followingData, setfollowingData] = useState();
const [loading, setLoading] = useState(true);
// Following counts, displayname, image
const fetchData = useCallback(() => {
const dataRef = firestore().collection('usernames');
dataRef
.doc(urlname)
.collection('Following')
.limit(25)
.onSnapshot((snapshot) => {
let promises = [];
snapshot.forEach((doc) => {
const promise = dataRef
.doc(doc.id.toLowerCase())
.get()
.then((followerDoc) => {
const data = followerDoc.data();
return {
profileName: doc.id,
displayName: data.displayName
? data.displayName
: data.userName,
followerCount:
data.followers !== undefined ? data.followers : 0,
followingCount:
data.following !== undefined ? data.following : 0,
image: data.imageUrl ? data.imageUrl : null,
};
});
promises.push(promise);
});
Promise.all(promises)
.then((res) => setfollowingData(res))
.then(setLoading(false));
});
}, []);
useEffect(() => {
const dataRef = firestore().collection('usernames');
const cleanup = dataRef
.doc(urlname)
.collection('Following')
.limit(25)
.onSnapshot(fetchData);
return cleanup;
// fetchData();
}, [urlname, fetchData]);
return (
<>
<View
style={styles}>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon name="menu" color="#222" size={30} />
</TouchableOpacity>
<Text style={{left: width * 0.05}}>Following</Text>
</View>
{loading ? (
<ActivityIndicator size="large" color="black" />
) : (
<>
<FolloweringScreens data={followingData} />
</>
)}
</>
);
}
export default Following;

Categories

Resources