简体   繁体   中英

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;

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

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:

  1. Make a state variable to store the data.

     const [topSong, setTopSong] = useState("");
  2. 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(); }, []);
  3. Use this state in your render method now.

     return ( <View> <Text>StatsScreen</Text> <Text>{topSong}</Text> </View> );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM