7

I'm trying to learn React and I'm a beginner when it comes to Javascript. Right now I'm working on an app that is fetching data from Flickr's API. The problem is that when I try to use the map method on the props in the Main.js component I get an error saying "Uncaught TypeError: this.props.photos.map is not a function". After searching here on Stackoverflow I think the problem is that this.props are javascript objects and not an array. The problem is that I can't figure out how to make it an array. Can anyone explain what I'm doing wrong?

My code:

class App extends Component {

  constructor() {
  super();
  this.state = {

  }
}

componentDidMount() {

let apiKey = 'xxxxxxxxxxxxxxxxxx';
let searchKeyword = 'nature';
let url = `https://api.flickr.com/services/ 
           rest/?api_key=${apiKey}&method=flickr.photos.
           search&format=json&nojsoncallback=1&&per_page=50
           &page=1&text=${searchKeyword}`;

fetch(url)
  .then(response => response.json())
  .then(data => data.photos.photo.map((x) => {

    this.setState({
      farm: x.farm,
      id: x.id,
      secret: x.secret,
      server: x.server})
   // console.log(this.state)
  }))
 }

    render() {
      return (
        <div className="App">
          <Header />
          <Main img={this.state.photos} />
          <Navigation />
        </div>
      );
    }
  }

  export default class Main extends Component {

  render() {

    return(
      <main className="main">
        {console.log(this.props.photos)}
      </main>
    )
  }
 }

Edit: Why is this.props.img undefined first?

Screen shot from console.log(this.props.img)

Tose
  • 83
  • 1
  • 1
  • 5
  • what is `photo`? `map` is a method of `Array`. – Davin Tryon Jun 15 '17 at 18:22
  • *'I get an error saying "Uncaught TypeError: this.props.photos.map is not a function"'* `this.props.photos.map` doesn't appear anywhere in your quoted code. `data.photos.photo.map` does, did you mean that? – T.J. Crowder Jun 15 '17 at 18:25
  • 1
    Separately: You're calling `map` and within the `map` callback, you're calling `setState` and not returning any value, so you're mapping every entry to `undefined`; you're also not using the return value of `map` at all (you're returning it out of `then`, but nothing uses the resulting promise, so it goes unused). You almost certainly don't want to repeatedly call `setState` like that. – T.J. Crowder Jun 15 '17 at 18:27
  • Does the URL really have linebreaks? That would not work properly. – Patrick Roberts Jun 15 '17 at 18:30

3 Answers3

7
fetch(url)
  .then(response => response.json())
  .then(data => data.photos.photo.map((x) => {

    this.setState({
      farm: x.farm,
      id: x.id,
      secret: x.secret,
      server: x.server})
  }))

What is happening is that your map function in your promise is resetting the component's state for every photo that is returned. So your state will always be the last object in your list of returned photos.

Here is a more simplified example of what I am referring to

const testArray = [1,2,3,4];

let currentState;

testArray.map((value) => currentState = value)

console.log(currentState);

What you want to do is this

const testArray = [1,2,3,4];

let currentState;

//Notice we are using the return value of the map function itself.
currentState = testArray.map((value) => value)

console.log(currentState);

For what you are trying to accomplish, you want your state to be the result of the map function (since that returns an array of your results from the map). Something like this:

fetch(url)
  .then(response => response.json())
  .then(data => 
    this.setState({
      photos:
        data.photos.photo.map((x) => ({
          farm: x.farm,
          id: x.id,
          secret: x.secret,
          server: x.server
        }))
     })
   )
Nick Wyman
  • 1,150
  • 11
  • 18
  • Thank you for the answer. I still get the error message "map is not a function" when I try to use the map method on this.props in the Main component. What I want to do is create a new img-tag for every object in this.state.
    {this.props.photos.map((x) => { return })}
    – Tose Jun 15 '17 at 19:09
  • If you console this.props.photos, what does it return? – Nick Wyman Jun 15 '17 at 19:23
  • 1
    A list of objects. Is the problem that it's not an array? Object {0: Object, 1: Object, 2: Object, 3: Object, 4: Object, 5: Object, 6: Object, 7: Object, 8: Object, 9: Object, 10: Object, 11: Object, 12: Object, 13: Object, 14: Object, 15: Object, 16: Object, 17: Object, 18: Object, 19: Object, 20: Object, 21: Object, 22: Object, 23: Object, 24: Object, 25: Object, 26: Object, 27: Object, 28: Object, 29: Object, 30: Object, 31: Object, 32: Object, 33: Object, 34: Object, 35: Object, 36: Object, 37: Object, 38: Object} – Tose Jun 15 '17 at 19:34
  • Digging in, it looks like React's state needs to be an object. I've updated my solution at the bottom to reflect that. Essentially, just add the array into a property and pass that property to your child component. – Nick Wyman Jun 15 '17 at 19:45
  • I really appreciate all the help and sorry for all the questions. I still get the same error when trying to map. Can it be because this.props.img in the first place is undefined (see image uploaded in the bottom of my first question)? Notice that I changed the property name from photos to img. – Tose Jun 15 '17 at 20:31
  • Probably. The reason it is undefined at first is because your fetch function is asynchronous. So what happens is that your App component is rendered before the fetch function finishes. This is where your undefined result is coming from. Once the fetch finishes, it populates the state and re-renders your App component. That is the second console log. You can solve this in one of two ways. 1. set the array in your state to an empty array in the App component constructor. 2. Update your Main component to handle a possible undefined value in this.props.img. – Nick Wyman Jun 15 '17 at 20:38
1

This error might also happen if you try to provide something else other than the array that .map() is expecting, even if you declare the variable type properly. A hook-based example:

const [myTwinkies, setMyTwinkies] = useState<Twinkies[]>([]);

useEffect(() => {
  // add a twinky if none are left in 7eleven
  // setMyTwinkies(twinkiesAt711 ?? {}); // ---> CAUSES ".map is not a function"
  setMyTwinkies(twinkiesAt711 ?? [{}]); 
}, [twinkiesAt711, setMyTwinkies]);

return (<ul>
  {myTwinkies.map((twinky, i)=> (
    <li key={i}>Twinky {i}: {twinky?.expiryDate}</li>
  ))}
</ul>)
CPHPython
  • 12,379
  • 5
  • 59
  • 71
0

Just check the length of the array before going for the map. If the len is more than 0 then go for it, otherwise, ignore it.

data.photos.photo.map.length>0 && data.photos.photo.map(........)
Mujahidul Islam
  • 265
  • 4
  • 8