简体   繁体   中英

Mapping through state: (TypeError): this.state.[something].map is not a function

I'm trying to map through my state, where I have collected data from an external API.

However, when I do i get this error here: TypeError: this.state.stocks.map is not a function

I want to render the results to the frontend, through a function so that the site is dynamic to the state.favorites. Though the console.log(), I can see that the data is stored in the state.

I have found others with a similar issue, but the answers did not work out.

UPDATE: I have changed the componentDidMount() and it now produces an array. The issue is that I get no render from the renderTableData() functions.

console.log shows this array:

0: {symbol: "ARVL", companyName: "Arrival", primaryExchange: "AESMSBCDA)LNKOS/ TLTE(N GAEQLGAR ", calculationPrice: "tops", open: 0, …}
1: {symbol: "TSLA", companyName: "Tesla Inc", primaryExchange: " RNK EAASGTDACLN)LE/OGMELAQSTB (S", calculationPrice: "tops", open: 0, …}
2: {symbol: "AAPL", companyName: "Apple Inc", primaryExchange: "AMTGS/C) AALGDRSTNLEOEL(S BAE NQK", calculationPrice: "tops", open: 0, …}
length: 3
__proto__: Array(0)

This is my code:

import React, { Component } from 'react'
import './Table.css';


class Table extends Component {
  constructor (props) {
     super(props);
      this.state = {
         favorites: ['aapl', 'arvl', 'tsla'],
         stocks: []
     };
 }

  componentDidMount() {
   this.state.favorites.map((favorites, index) => {
   fetch(`API`)
       .then((response) => response.json())
       .then(stockList => {
         const stocksState = this.state.stocks;
         const stockListValObj = stockList;
         console.log(stocksState)
         console.log(stockListValObj)
         this.setState({
            stocks: [
                ... stocksState.concat(stockListValObj)
            ]
          }, () => { console.log(this.state.stocks);});
      })
   })
 }


renderTableData() {
    this.state.stocks.map((stocks, index) => {
      const { companyName, symbol, latestPrice, changePercent, marketCap } = stocks //destructuring
      return (
        <div key={symbol} className='headers'>
          <div className='first-value'>
            <h4>{companyName}</h4>
            <h4 className='symbol'>{symbol}</h4>
          </div>
          <div className='align-right'>
            <h4>{latestPrice}</h4>
          </div>
          <div className='align-right'>
            <h4 className='changePercent'>{changePercent}</h4>
          </div>
          <div className='align-right'>
            <h4>{marketCap}</h4>
          </div>
        </div>
      );
   })
}


   render() {
      return (
         <div className='table'>
            <h1 id='title'>Companies</h1>
            <div className='headers'>
              <h4 className='align-right'></h4>
              <h4 className='align-right'>Price</h4>
              <h4 className='align-right'>+/-</h4>
              <h4 className='align-right'>Market Cap</h4>
            </div>

            <div>
              {this.renderTableData()}
            </div>

         </div>
      )
   }
 }

export default Table;

I would say that this is happening because on the first render, the map looks into state.stock and it's an empty array. After the first render, the componentDidMount method is called fetching the data.

I would suggest to just wrap the map into a condition. If stock doesn't have any object, then return whatever you wish (null or a loader/spinner for example).

It's enough to add it like this for not returning anything in case the array is empty (it will be filled after the first render, but this is useful as well to return error message in case the fetch fails):

this.state.stocks.length > 0 && this.state.stocks.map((stocks, index) => {

You should always aim in making single state update, try reducing the state update to single update.

I suggest 2 solution:

  1. Move the data fetch section into a separate function, update a temporary array variable return the variable at the end of the execution.

  async dataFetch() {
    const sampleData = this.state.stocks || [];
    await this.state.favorites.forEach((favorites, index) => {
      fetch(`API`)
        .then((response) => response.json())
        .then((stockList) => {
          sampleData.push(stockList);
          // const stocksState = this.state.stocks;
          // const stockListValObj = stockList;
          // console.log(stocksState);
          // console.log(stockListValObj);
          //  this.setState({
          //     stocks: [
          //         ... stocksState.concat(stockListValObj)
          //     ]
          //   }, () => { console.log(this.state.stocks);});
        });
    });
    return Promise.resolve(sampleData);
  }

  componentDidMount() {
    this.dataFetch().then((stockValues) => {
      this.setState({ stocks: stockValues });
    });
  }

  1. Use Promise.all() this return a single array value which would be easier to update on to the stock state.

Suggestion: When not returning any values from the array try using Array.forEach instead of Array.map

Return keyword is missing in renderTableData

renderTableData() {
    this.state.stocks.map((stocks, index) => { ...});

to

renderTableData() {
   return this.state.stocks.map((stocks, index) => { ...});

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