map array of objects from an api react

Viewed 7515

I have been trying to get the images from this API to append to the page by mapping through them, but I keep getting one of two error messages saying 'undefined.map is not a function' or 'getBirds.map is not a function.'I've tried leaving the array of objects as it is and setting state to an object and an array(at separate times) but that didn't work. I've also tried using Object.key, Object.values, and Object.entries(each at separate times) to turn the array of objects into an array and then map through my variable and through getBirds(again separately) but those attempts also failed. I have attached three of my attempts below. Can someone help me understand where I've gone wrong?

// Attempt 1
import {useState, useEffect} from 'react'
import axios from 'axios'

function Birds(props) {
   const [getBirds, setGetBirds] = useState({})
   const {image} = props

   useEffect(() => {
      async function fetchBirds() {
         const URL = `https://audubon-society-api.herokuapp.com/birds`
         try {
            const res = await axios.get(URL)
            console.log(res.data)
            setGetBirds(res.data)
         } catch (error) {
            console.log(error)
         }
      }
      fetchBirds()
   }, [])
   
   if (!getBirds) return <h3>Loading...</h3>

   return (
      <div>
         <img src={getBirds.map(image)} alt={getBirds.map(image)}></img>
      </div>
   )
}

export default Birds

// Attempt 2
import {useState, useEffect} from 'react'
import axios from 'axios'

function Birds(props) {
   const [getBirds, setGetBirds] = useState([])
   const {image} = props

   useEffect(() => {
      async function fetchBirds() {
         const URL = `https://audubon-society-api.herokuapp.com/birds`
         try {
            const res = await axios.get(URL)
            console.log(res.data)
            setGetBirds(res.data)
         } catch (error) {
            console.log(error)
         }
      }
      fetchBirds()
   }, [])
   
   if (!getBirds) return <h3>Loading...</h3>

   return (
      <div>
         <img src={getBirds.map(image)} alt={getBirds.map(image)}></img>
      </div>
   )
}

export default Birds

// Attempt 3
import {useState, useEffect} from 'react'
import axios from 'axios'

function Birds(props) {
   const [getBirds, setGetBirds] = useState({})
   const {image} = props

   useEffect(() => {
      async function fetchBirds() {
         const URL = `https://audubon-society-api.herokuapp.com/birds`
         try {
            const res = await axios.get(URL)
            console.log(res.data)
            setGetBirds(res.data)
         } catch (error) {
            console.log(error)
         }
      }
      fetchBirds()
   }, [])

   const birds = Object.entries(getBirds)

   birds.forEach(([key, value]) => {
      console.log(key, value)
   })
   
   if (!getBirds) return <h3>Loading...</h3>

   return (
      <div>
         <img src={birds.map(image)} alt={birds.map(image)}></img>
      </div>
   )
}

export default Birds
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

2 Answers

You would need to initialize your state with an array, so the map function won't get errors, and correct the way you map it:

  • Initialize state with an array:
const [getBirds, setGetBirds] = useState([]);
  • Map it:
return (
  <div>
    {getBirds.map((bird) => (
      <img src={bird.image} alt={bird.image}></img>
    ))}
  </div>
);
  • Also, check your array with length, because [] or {} both equal to true.
if (!getBirds.length) return <h3>Loading...</h3>;

console.log(!![]);
console.log(!!{});

console.log(!![].length)

The completed solution:

import { useState, useEffect } from "react";
import axios from "axios";

function Birds(props) {
  const [getBirds, setGetBirds] = useState([]);

  useEffect(() => {
    async function fetchBirds() {
      const URL = 'https://audubon-society-api.herokuapp.com/birds';
      try {
        const res = await axios.get(URL);
        console.log(res.data);
        setGetBirds(res.data);
      } catch (error) {
        console.log(error);
      }
    }
    fetchBirds();
  }, []);

  if (!getBirds.length) return <h3>Loading...</h3>;

  return (
    <div>
      {getBirds.map((bird) => (
        <img src={bird.image} alt={bird.image}></img>
      ))}
    </div>
  );
}

export default Birds;

Working Example:

Edit focused-hertz-ux7im

Your init state of birds and setBirds should be an array [] not an object {}, also you don't need:

const birds = Object.entries(getBirds). fetch return array of birds already.

<img src={birds.map(image)} alt={birds.map(image)}></img> is wrong, the array loop map should render an image for each bird.

Below code will run for your need:

    import React, {useState, useEffect} from "react";
    import axios from 'axios';
    
    function Birds(props) {
      //- const [getBirds, setGetBirds] = useState([])
      //- const {image} = props
      // +
      const [birds, setGetBirds] = useState([])
      useEffect(() => {
         async function fetchBirds() {
            const URL = `https://audubon-society-api.herokuapp.com/birds`
            try {
               const res = await axios.get(URL)
               console.log(res.data)
               setGetBirds(res.data)
            } catch (error) {
               console.log(error)
            }
         }
         fetchBirds()
      }, [])
    
      // - const birds = Object.entries(getBirds)
    
      // - birds.forEach(([key, value]) => {
      // -   console.log(key, value)
      // - })
      
      // - if (!getBirds) return <h3>Loading...</h3>
      if (!birds) return <h3>Loading...</h3>
      return (
         <div>
          {/* <img src={birds.map(image)} alt={birds.map(image)}></img> */} 
           {birds.map((item, index) => 
            <img src={item.image} alt={index}></img>
            )}
         </div>
      )
    }
    
    export default Birds
Related