Xing Chen
Xing Chen

Reputation: 23

How to fix the "Type Error: Cannot read property 'map' of undefined?

I am trying to perform a fetch call to return an array however, when i try to use a map function to iterate the array the compiler gives an error saying that cannot read property map of undefined which i'm stuck and i also did some research on similar problems but to no avail. I am new in React here therefore i'm not sure which part causes the error. I realise that it comes from my setState function call.

This is my App.js code:


import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
 constructor()  {
     super();
     this.state={
        currencies: [],
        };



        }


handleChange =(event) => {

   let initialData = [];
        const url = `http://data.fixer.io/api/latest?access_key=ea263e28e82bbd478f20f7e2ef2b309f&symbols=${event.target.value}&format=1`

console.log("the url is: " + url)
 fetch(url).
  then(data =>{ return data.json();})
  .then(findData => {
   initialData = findData.rates
   console.log(initialData)
   this.setState({

        currencies: initialData.rates,

        });
});

}

  render() {
    const{currencies} = this.state; 
    return (
      <div className="App">
    { this.state.currencies.map((current) => <div> {current.rates}</div>)}  


        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
     <h1 className="App-title"> Welcome to DKK website </h1>

        <div class="dropdown">
          <select id="select1" name ="currency" value={this.state.selectValue} onChange={this.handleChange}>
                <option value="EUR">-- Selecting: NILL --</option>
                <option value="CAD">-- Selecting: CAD --</option>
                <option value="SGD">-- Selecting: SGD --</option>
                <option value="AFN">-- Selecting: AFN --</option>
        </select>


        </div>


<button className="pressMe" > Set Button </button>
<br/>
<br/>


     <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

export default App;

Upvotes: 2

Views: 2083

Answers (2)

It&#39;sNotMe
It&#39;sNotMe

Reputation: 1260

Your API call returns a promise, which means there's no array to map over immediately. Use Async/Await to wait for the API call to complete. Once the data comes back from the API you can save it to state.

This code sample renders the results. When the currency selection changes, an API call is made. Async/Await handles the promise and saves the results to state. React "reacts" by rendering the results as soon as the state changes.

The API returns a different rates object for every currency. Because those results are unpredictable, Object.keys is used to access the name and value of the unknown response object. See more on Object.keys.

You use destructuring to access this.state.currencies, so you can simply refer to it as currencies in the remainder of your component. Finally, because you attached the handleChange event to your select tag, the button isn't needed.

import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";

class App extends Component {
  constructor() {
    super();
    this.state = {
      currencies: []
    };
  }

  handleChange = event => {
    this.getData(event.target.value);
  };

  async getData(target) {
    const url = `http://data.fixer.io/api/latest?access_key=ea263e28e82bbd478f20f7e2ef2b309f&symbols=${target}&format=1`;

    console.log("the url is: " + url);
    let data = await fetch(url).then(data => {
      return data.json();
    });
    this.setState({ currencies: data });
  }

  render() {
    const { currencies } = this.state;
    console.log(this.state);
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <h1 className="App-title"> Welcome to DKK website </h1>

          <div class="dropdown">
            <select
              id="select1"
              name="currency"
              value={this.state.selectValue}
              onChange={this.handleChange}
            >
              <option value="EUR">-- Selecting: NILL --</option>
              <option value="CAD">-- Selecting: CAD --</option>
              <option value="SGD">-- Selecting: SGD --</option>
              <option value="AFN">-- Selecting: AFN --</option>
            </select>
          </div>

          {/* <button className="pressMe"> Set Button </button> */}
          <br />

          <ul>
            <li>Base: {currencies.base}</li>
            <li>Date: {currencies.date}</li>
            <li>Timestamp: {currencies.timestamp}</li>
            {/* Sometimes currencies.rates is undefined.
            Use conditional rendering to prevent errors when there is no value */}
            {currencies.rates && (
              <li>
                Rate for {Object.keys(currencies.rates)}:{" "}
                {Object.values(currencies.rates)}
              </li>
            )}
          </ul>

          {/*
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
           */}
        </header>
      </div>
    );
  }
}

export default App;

Upvotes: 0

user3737430
user3737430

Reputation:

Your API call returns a promise base json data, which means there's no array to map over immediately use Object.keys form extracting keys for object for Array.map function. check blow code, also some issues fixed in this code.

import React, { Component } from "react";

class App extends Component {
  state = {
    loader: false,
    currencies: []
  };

  handleChange = event => {
    const val = event.target.value;
    this.setState({
      selectValue: val
    });
  };

  fetchData = () => {
    this.setState({
      loader: true
    });
    let initialData = [];
    const url = `http://data.fixer.io/api/latest?access_key=ea263e28e82bbd478f20f7e2ef2b309f&symbols=${
      this.state.selectValue
    }&format=1`;

    console.log("the url is: " + url);
    fetch(url)
      .then(data => {
        return data.json();
      })
      .then(findData => {
        initialData = findData.rates;
        this.setState({
          currencies: initialData,
          loader: false
        });
      })
      .catch(err => console.log(err));
  };

  render() {
    const { currencies, loader } = this.state;
    let list = null;
    if (loader) {
      list = "loading...";
    } else if (!loader && currencies) {
      list = Object.keys(currencies).map(current => (
        <div key={current}>
          {current}: {currencies[current]}
        </div>
      ));
    }
    return (
      <div className="App">
        <header className="App-header">
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <h1 className="App-title"> Welcome to DKK website </h1>
          {list}
          <div className="dropdown">
            <select
              id="select1"
              name="currency"
              value={this.state.selectValue}
              onChange={this.handleChange}
            >
              <option value="EUR">-- Selecting: NILL --</option>
              <option value="CAD">-- Selecting: CAD --</option>
              <option value="SGD">-- Selecting: SGD --</option>
              <option value="AFN">-- Selecting: AFN --</option>
            </select>
          </div>

          <button className="pressMe" onClick={this.fetchData}>
            Set Button
          </button>
          <br />
          <br />

          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

export default App;

Upvotes: 2

Related Questions