I am using a Spotify API to fetch songs and I am trying to display the first song for testing.
I am trying to display the first song as text but currently I am getting the error "Objects are not valid as a React child (found: Object with keys {_U, _V, _W, _X}). If you meant to render a collection of children use a array instead.
I am confused as I am just trying to the first thing from the JSON and display it as a text on the stats screen
import React, {useState} from "react";
import axios from "axios";
import { getSpotifyToken } from "../../hooks/spotifyAuth";
const getTopTracks = async () => {
//Getting spotify token
const spotifyToken = getSpotifyToken();
console.log("Getting access Token for TopSongs:", spotifyToken );
// const [songName, setSongName] = useState("");
const api_url = "https://api.spotify.com/v1/me/top/tracks?time_range=short_term&limit=5";
// const api_url = "https://api.spotify.com/v1/me/top/artists?time_range=short_term&limit=1&offset=5";
// console.log(api_url);
try{
const response = await axios.get(api_url, {
headers: {
'Authorization': `Bearer ${spotifyToken}`
}
});
const myJSON = response.data.items[0].name.toString();
console.log("My JSON:", myJSON); //this just prints the song name
return myJSON;
}catch(error){
console.log(error);
}
};
const StatsScreen = ({ navigation }) => {
const topSong = getTopTracks();
return (
<View>
<Text>StatsScreen</Text>
<Text>{topSong}</Text>
</View>
);
};
export default StatsScreen;
CodePudding user response:
Because topSong
is a promise, not a string. async
functions always return promises (more here).
If you want StatsScreen
to retrieve the top song, you'll need to make it stateful, since initially it won't have a song to show:
const StatsScreen = ({ navigation }) => {
const [topSong, setTopSong] = useState(null);
useEffect(() => {
getTopSongs()
.then(setTopSong)
.catch((error) => {
// ...handle/report error...
})
}, []); // <== Empty deps array = only on mount
return (
<View>
<Text>StatsScreen</Text>
{topSong && <Text>{topSong}</Text>}
</View>
);
};
That fetches the top song on mount via useEffect
, and stores the result as state using useState
. I've had it not render the second Text
at all when it doesn't have one, but of course you can tweak that as desired, for instance:
return (
<View>
<Text>StatsScreen</Text>
<Text>{topSong ? topSong : "Loading top song..."}</Text>
</View>
);
};
A more robust version can use an AbortController
(in axios v0.22.0 and up; for earlier versions, use the deprecated axios-specific CancelToken
) to cancel the outstanding HTTP request if the component is unmounted while it's running:
const getTopTracks = async (signal) => {
// ^^^^^^ <====
// ...
try {
const response = await axios.get(api_url, {
signal, // <====
headers: {
Authorization: `Bearer ${spotifyToken}`,
},
});
// ...
} catch (error) {
console.log(error);
}
};
const StatsScreen = ({ navigation }) => {
const [topSong, setTopSong] = useState(null);
useEffect(() => {
const controller = new AbortController(); // <====
getTopSongs(controller.signal) // <====
.then(setTopSong)
.catch((error) => {
// ...handle/report error...
});
return () => { // <====
// Called on unmount // <====
controller.abort(); // <====
};
}, []); // <== Empty deps array = only on mount
return (
<View>
<Text>StatsScreen</Text>
{topSong && <Text>{topSong}</Text>}
</View>
);
};
CodePudding user response:
Your getTopTracks()
is returning a promise as you have not await-ed the function call. Also you haven't called the API at the right place.
API calls are asynchronous and so the data comes after the component has rendered. So the basic flow for any API call like this has to be:
Make a state variable to store the data.
const [topSong, setTopSong] = useState("");
Call the API in a
useEffect
(If the data is to be fetched only once, you may use an empty dependency array)useEffect(() => { const fetchTopSong = async () => { const response = await axios.get(...); setTopSong(response.data.items[0].name); } fetchTopSong(); }, []);
Use this state in your render method now.
return ( <View> <Text>StatsScreen</Text> <Text>{topSong}</Text> </View> );