Why am I getting Object are not valid as a React child

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;

>Solution :

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>
    );
};

Leave a Reply