Exercise - Refresh the Prices on Click

Thanks for getting back to me… GitHub link:
https://github.com/jonsax6/coin-exchange

Let me know what you see!

Hi @jonsax

I’ve found your issue.

There was a mistake in the tickerId passing in your handleRefresh call. Instead of calling https://api.coinpaprika.com/v1/tickers/btc-bitcoin, you were calling https://api.coinpaprika.com/v1/tickers/BTC. It is the incorrect API URL and since coinparika has a CORS policy on any of their server method calls, it was restricting you from accessing the non-public API URLs.

This issue was found at line number 14 of Coin.jsx component.

Previously it was this.props.handleRefresh(this.props.ticker)
I changed it to this.props.handleRefresh(this.props.tickerId); and it worked as expected.

Always analyse what URLs you are hitting through the network tab to properly debug your application.

Hope this helps.

1 Like

Thanks man!

By the way, I’m curious why I can’t increase my coin count to anything larger than 10? Do you know why this would cause a bug? If I wanted to have the count be 50 or 100, it throws an error. Even changing from 10 to 11 throws a 429 error. Is 10 the maximum number of requests for https://api.coinpaprika.com/v1/tickers/ ??

Debugger throws at: const promises = coinIds.map(id => axios.get(tickerUrl + id));

Same problem here, thanks for the catch!!

yeah, guessing that’s why he chose to limit the number of rows to 10. Maybe the API request throws a 429 after 10 times in a short period of time. But a market price feed page with only 10 rows isn’t very useful real-world actual. For the exercise it’s all good I guess. Anyway some confirmation from admin’s on this would be helpful in ruling out a bug vs API request limit… What say you admins!!?

Yes, they limit their API calls if you’re using their APi’s for free. However, if you buy their subscription they will allow a more elaborate throughput and can use that in the production environment.

Hope this helps.

const tickerUrl = 'https://api.coinpaprika.com/v1/tickers/'

refreshPrice = (targetTicker) => {
      //const coin = this.state.coinData.find( ({ticker}) => ticker === targetTicker )
      //console.log(coin)

      // Zsolt Nagy solution: it follows IMMUTABILITY 'cause it make a copy of the changed object (coinData)
      const newCoinData = this.state.coinData.map ( ( values ) => {
        // Shallow copy http://www.zsoltnagy.eu/cloning-objects-in-javascript/
        let newValues = { ...values }
        if ( targetTicker === values.ticker ) {
          //const tickerUrl = 'https://api.coinpaprika.com/v1/tickers/'          
          axios.get(tickerUrl + values.key)
          .then( response => {
            const coin = response.data
            console.log("App: tickerUrl", coin)
            newValues.price = parseFloat(Number(coin.quotes.USD.price).toFixed(5))
          })
          //const randomPercentage = 0.995 + Math.random() *0.01
          //newValues.price *= randomPercentage    
        }
        return newValues
      } )
      this.setState( {coinData: newCoinData} )
}
2 Likes

Like a couple others in here, I spent many hours trying to force ‘key’ as a prop into Coin and lift it up to the App.js refreshHandler to check against ‘values.key’, but that is not allowed!

After learning from you guys, I passed a new prop 'id={key} into my Coin component and lifted that to refreshHandler instead.

App.js
class App extends React.Component {
  state = {
    balance: 10000,
    showBalanceState: true,
    coinData: []
  }
  componentDidMount = async () => {
    const response = await axios.get(CoinsURL);
    const coinIds = response.data.slice(0,CoinCount).map(coin => coin.id);
    const promises = coinIds.map((id) => axios.get(CoinsURL + id));
    const coinData  = await Promise.all(promises);
    const coinDetails = coinData.map(function(response) {
      const coin = response.data;
      return{
        key: coin.id,
        name: coin.name,
        ticker: coin.symbol.toUpperCase(),
        balance: 0,
        price: parseFloat(Number(coin.market_data.current_price.usd).toFixed(4)),
      }
    });

      this.setState({coinData: coinDetails});
    };   
    handleShowBalance = () => {
      if(this.state.showBalanceState){this.setState({showBalanceState: false});}
      else if(!this.state.showBalanceState){this.setState({showBalanceState: true});}
    }

    handleRefresh = async (valueChangeId) => {
      const response = await axios.get(CoinsURL + valueChangeId);
      const newCoinData = this.state.coinData.map( function( values ) {
        let newValues = {...values};  // copies 'values' so we can change any as needed
        if (valueChangeId === values.key){
          newValues.price = parseFloat(Number(response.data.market_data.current_price.usd).toFixed(4))
        }
          return newValues;
       });
      this.setState({coinData: newCoinData});
    };
  
  render(){
    return (
      <Div className="App">
        <Header/>
        <AccountBalance amount={this.state.balance}
                        showBalance={this.state.showBalanceState} 
                        handleShowBalance={this.handleShowBalance}/>
        <CoinList coinData={this.state.coinData}
                  showBalance={this.state.showBalanceState}
                  handleRefresh={this.handleRefresh}
                  handleShowBalance={this.handleShowBalance}/>
      </Div>
    );
  }
}

export default App;


CoinList.js
export default class CoinList extends Component {
    render() {
      const balance = this.props.showBalance ?
        <th>Balance</th> : null;
        return (
        <CoinTable>
            <thead>
                <tr>
                  <th>Name</th>
                  <th>Ticker</th>
                  <th>Price</th>
                  <th>Action</th>
                  {balance}
                </tr>
            </thead>
            <tbody> 
              {
                this.props.coinData.map(({key,name,ticker,price,balance}) =>
                 <Coin  key={key}
                        id={key}
                        handleRefresh={this.props.handleRefresh} 
                        name={name} 
                        ticker={ticker} 
                        price={price}
                        balance={balance}
                        showBalance={this.props.showBalance} /> 
                  )
                }
            </tbody>
        </CoinTable>
        )
    }
}

Coin.js
export default class Coin extends Component {
    handleClick = (event) => {
        event.preventDefault();
        this.props.handleRefresh(this.props.id);
    }

    render(){
        const balance = this.props.showBalance ?
            <Td>{this.props.balance}</Td> : null;

        return(
            <tr>
                <Td>{this.props.name} </Td>
                <Td>{this.props.ticker} </Td>
                <Td>${this.props.price} </Td>
                <Td>
                    <form>
                        <button onClick={this.handleClick}>Refresh</button>
                    </form>
                </Td>
                {balance}
            </tr>
        );
    }
} 

Coin.propTypes = {
    name: PropTypes.string.isRequired,
    ticker: PropTypes.string.isRequired,
    price: PropTypes.number.isRequired,
    balance: PropTypes.number.isRequired
}


2 Likes

key is a special property that is mandatory for rendering lists (arrays) of components. When you use the name key, it won’t be available in the props. Also be careful about using id, because it is the HTML id attribute that has to be unique in a document.

1 Like

const COIN_COUNT = 10;
const coinsUrl = ‘https://api.coinpaprika.com/v1/coins’;
const tickerUrl = ‘https://api.coinpaprika.com/v1/tickers/

class App extends React.Component {
state = {
balance: 10000,
showBalance: true,
coinData: []
};
componentDidMount = async () => {
const response = await axios.get(coinsUrl);
const coinIds = response.data.slice(0, COIN_COUNT).map(coin => coin.id);
const promises = coinIds.map(id => axios.get(tickerUrl + id));
const coinData = await Promise.all(promises);
const coinPriceData = coinData.map(function (response) {
const coin = response.data;
return {
key: coin.id,
name: coin.name,
ticker: coin.symbol,
balance: 0,
price: parseFloat(Number(coin.quotes[‘USD’].price).toFixed(4)),
};
})
//Retrieve the prices
this.setState({ coinData: coinPriceData });
};

handleRefresh = async (valueChangeKey) => {
const coinValue = valueChangeKey
console.log(coinValue);
const keyData = await axios.get(tickerUrl + valueChangeKey);
console.log(keyData);
const newCoinData = this.state.coinData.map(function (values) {
let newValues = { …values };
if (valueChangeKey === values.key) {

    newValues.price = parseFloat(Number(keyData.data.quotes['USD'].price).toFixed(2))
  };
  return newValues;

});

this.setState({ coinData: newCoinData });

}
handleToggleShowBalance = () => {
this.setState(function (oldState) {
return {
…oldState,
showBalance: !oldState.showBalance
};
});
}

render() {

return (
  <Div>
    <Header />
    <AccountBalance
      amount={this.state.balance}
      showBalance={this.state.showBalance}
      handleToggleShowBalance={this.handleToggleShowBalance} />
    <CoinList
      coinData={this.state.coinData}
      handleRefresh={this.handleRefresh}
      showBalance={this.state.showBalance} />
  </Div>
);

};
}

export default App

2 Likes

I’m using this course to help me build my Dex project from the Smart Contract 202 course, and I’ve used the coingecko api instead…so here is my code :slight_smile:

  getGecko = async () => {
    let url = "https://api.coingecko.com/api/v3/simple/price?ids=ethereum%2Cchainlink&vs_currencies=gbp,eth&include_market_cap=true&include_24hr_change=true"
    let response = await axios.get(url);
      let jsonData = JSON.stringify(response);
      this.setGecko(jsonData);
    };
  
  setGecko = (data) => {
    let info = JSON.parse(data);
    let coinData = [
            {
              name: 'Ethereum',
              ticker: 'ETH',
              priceETH: 1,
              priceGBP: info.data.ethereum.gbp              
            },
            {
              name: 'Chainlink',
              ticker: 'LINK',
              priceETH: info.data.chainlink.eth,
              priceGBP: info.data.chainlink.gbp              
            }
          ];
    this.setState({ coinData });
  };

and then in the componentDidMount function…

this.getGecko();
      const callback = () => {
          this.getGecko();
        };
      setInterval(callback,10000);

So the prices refresh every 10 seconds.

1 Like

Hi, @n1g3.

Splendid!

Keep up the great work! :muscle:

Happy Learning!

This was pretty confusing for me but I’m finally starting to understand.
in CoinList.jsx we add a new property id = {key}.

In Coin.jsx we pass this.props.handleRefresh(this.props.id); in the handleClick function. So the coins key is passed to app.js since id = key.

in app.js the coins id prop is passed to (ValueChangeId). this way we can just add it to the end of our api call to get the right coin info, ex : axios.get(tickerUrl + valueChangeId);

also we use ValueChangeid (which is a certain coins key) with values.key and whichever one is matching is how the code knows to update price with our if statement.

app.js
import React from 'react';

import CoinList from './components/CoinList/CoinList';

import AccountBalance from './components/AccountBalance/AccountBalance';

import Header from './components/Header/Header';

import styled from 'styled-components';

import axios from 'axios';

const Content = styled.div`

text-align: center;

  background-color: rgb(82, 79, 79);

  color: rgb(180, 178, 19);

`;

const COIN_COUNT = 10;

const tickerUrl = 'https://api.coinpaprika.com/v1/tickers/';

class App extends React.Component {

  state = {

    balance: 10000,

    showBalance: true,

    coinData: [

/*

      {

        name: 'Bitcoin',

        ticker: 'Btc',

        balance: 0.5,

        price: 9999.99

      },

      {

        name: 'Ethereum',

        ticker: 'Eth',

        balance: 35,

        price: 2999.99

      },

      {

        name: 'Tether',

        ticker: 'USDT',

        balance: 0.5,

        price: 1.0

      },

      {

        name: 'Ripple',

        ticker: 'XRP',

        balance: 1000,

        price: 1.3

      },

      {

        name: 'Bitcoin Cash',

        ticker: 'BCH',

        balance: 0,

        price: 298.99

      }, */

    ]

  }

  componentDidMount = async () => {

  const response = await axios.get('https://api.coinpaprika.com/v1/coins');

  const coinIds = response.data.slice(0, COIN_COUNT).map(coin => coin.id);

  

  const promises = coinIds.map(id => axios.get(tickerUrl + id));

  const coinData = await Promise.all(promises);   

  const coinPriceData = coinData.map(function(response) {

    const coin = response.data;

    return {

      key: coin.id,

      name: coin.name,

      ticker: coin.symbol,

      balance: 0,

      price: parseFloat(Number(coin.quotes.USD.price).toFixed(4)),

    };

  })

  // Retrieve the prices

  this.setState({ coinData: coinPriceData });     

  }

  handleRefresh = async (valueChangeId) => {

    let response = await axios.get(tickerUrl + valueChangeId);

    const refreshedData = response.data;

    const newCoinData = this.state.coinData.map( function( values ) {

      let newValues = {...values};

      if (valueChangeId === values.key) {

        newValues.price = parseFloat(Number(refreshedData.quotes['USD'].price).toFixed(2));

      }

      return newValues;

    });

    this.setState({ coinData: newCoinData });

  }

     handleBalanceToggle = () => {

       this.setState( function(oldState) {

         return {

           ...oldState,

           showBalance: !oldState.showBalance

         }

       });

     }

  render() {

    return (

      <Content>

        <Header />

        <AccountBalance amount={this.state.balance}

          showBalance={this.state.showBalance}

          handleBalanceToggle={this.handleBalanceToggle} />

       <CoinList coinData={this.state.coinData}

        handleRefresh={this.handleRefresh}

        showBalance={this.state.showBalance} />

      </Content>

    );

  }

  

}

export default App;
Coin.jsx
import React, { Component } from 'react';

import PropTypes from 'prop-types';

import styled from 'styled-components';

const CoinRow = styled.td`

border: 1px solid;

    width: 25vh;

`;

export default class Coin extends Component {

    

    handleClick = (event) => {

        // Prevent the default action of submitting the form

        event.preventDefault();

        this.props.handleRefresh(this.props.id);

    }  

    render() {

        return (

            <tr>

              <CoinRow>{this.props.name}</CoinRow>

              <CoinRow>{this.props.ticker}</CoinRow>

              <CoinRow>${this.props.price}</CoinRow>

              {this.props.showBalance ? <CoinRow>{this.props.balance}</CoinRow> : null}

              <CoinRow>

                  <form action="#" method="POST">

                  <button onClick={this.handleClick}>Refresh</button>

                  </form>            

              </CoinRow>

            </tr>

           );

    }

}

Coin.propTypes = {

    name: PropTypes.string.isRequired,

    ticker: PropTypes.string.isRequired,

    price: PropTypes.number.isRequired,

    balance: PropTypes.number.isRequired

}
CoinList.jsx
import React, { Component } from 'react';

import Coin from "../Coin/Coin";

import styled from 'styled-components';

const Table = styled.table`

margin: 50px auto 50px auto;

  display: inline-block;

  font-size: 1.4rem;

`;

export default class CoinList extends Component {

    render() {

        return (

            <Table>

        <thead>

          <tr>

            <th>Name</th>

            <th>Ticker</th>

            <th>Price</th>

            {this.props.showBalance ? <th>Balance</th> : null}

            <th>Actions</th>

          </tr>

        </thead>

        <tbody>

          {

            this.props.coinData.map( ({key, name, ticker, balance, price}) => 

            <Coin key={key} 

            id={key}

            handleRefresh={this.props.handleRefresh} 

            name={name} 

            ticker={ticker} 

            showBalance={this.props.showBalance}

            balance={balance}

            price={price}  />,

            )

          }

        </tbody>

      </Table>

        )

    }

}

also i got some help from the toshi times here

1 Like