Exercise - Refresh the Prices on Click

Welcome to the thread for the exercise Refresh the prices on Click

Coding exercise: Refresh the price of the coin upon clicking the Refresh button on the line of the coin.

FYI for doing name changes on a variable you can press F2 and VS code will change all the references for you.

App.js - Refresh Prices
  • I moved the API calls into separate functions
  • Call the API instead of calculating a random price change
  • The tricky part was using await Promise.all() as the API call turns the mapping into promises. You get undefined errors in the Coin component without it.
const API_BASE_URL = 'https://api.coinpaprika.com/v1';

  getCoinPrice = (id) => {
    return axios.get(`${API_BASE_URL}/tickers/${id}`);
  }

  handleRefresh = async (valueChangeticker) => {
    // generate the new state by cloning the old state
    // and updating the target coin price
    const responses = this.state.coinData.map(async values => {
      let newValues = { ...values }; // shallow copy
      
      if (values.ticker === valueChangeticker) {
        const response = await this.getCoinPrice(values.key);
        newValues.price = response.data.quotes['USD'].price;
      }

      return newValues;
    });
    const newCoinData = await Promise.all(responses);

    this.setState({ coinData: newCoinData });
  }

1 Like

Coding exercise: Refresh the price of the coin upon clicking the Refresh button on the line of the coin.

I modified the handleRefresh function to get and refresh the price with an api call for a specific coin id. To lift up the id from Coin to App, and as i can’t use key as props, in coinList i passed the id value to the Coin component. Thus Coin has a props.id that it can passed to App when the button is clicked.

App.js
import React from 'react';
import ExchangeHeader from './components/ExchangeHeader/ExchangeHeader';
import CoinList from './components/CoinList/CoinList';
import AccountBalance from './components/AccountBalance/AccountBalance';
import styled from 'styled-components';
import axios from 'axios';


const DivApp = styled.div`
    text-align: center;
    background-color: rgb(20, 56, 97);
    color: #cccccc;
    `;


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;
      //debugger;
      return {
        key: coin.id,
        name: coin.name,
        ticker: coin.symbol,
        balance: 0,
        price: parseFloat(Number( coin.quotes["USD"].price ).toFixed(2)) 
      };
    });

    this.setState({ coinData: coinPriceData });
  }


handleRefresh = async (valueChangekey) => {
  const keyData =  await axios.get( tickerUrl + valueChangekey);
  
  const newCoinData = this.state.coinData.map( function( values ) {
    let newValues = {...values};
    if (values.key === valueChangekey) {
      newValues.price = parseFloat(Number( keyData.data.quotes["USD"].price ).toFixed(2));
    };
  
  return newValues;
  });
  this.setState({ coinData: newCoinData });
}


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

  render() {
    return (
      <DivApp>
        <ExchangeHeader />
        <AccountBalance 
          amount={this.state.balance}
          handleToggleBalance={this.handleToggleBalance} 
          showBalance={this.state.showBalance} />
        <CoinList 
          coinData={this.state.coinData}
          handleRefresh={this.handleRefresh}
          showBalance={this.state.showBalance} />
      </DivApp>
    );
  }
}

export default App;
CoinList.jsx
import React, { Component } from 'react';
import Coin from '../Coins/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() {
      /*
        const toggleBalance = this.props.showBalance ?
        <th>Balance</th> : null;
      */

        return (
            <Table> 
            <thead>
              <tr>
                <th>Name</th>
                <th>Ticker</th>
                {this.props.showBalance ? <th>Balance</th> : null}
                <th>Price</th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              {
                this.props.coinData.map( ({key, name, ticker, balance, price}) =>
                  <Coin 
                  key={key} 
                  id={key}
                  name={name} 
                  handleRefresh={this.props.handleRefresh}
                  ticker={ticker}
                  showBalance={this.props.showBalance}
                  balance={balance}
                  price={price} 
                  />
                )
              }
            </tbody>
          </Table>
        )
    }
}```
Coin.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

const Td = styled.td`
    border: 1px solid #cccccc;
    width: 25vh;
`;
const Button = styled.button`
    height: 2rem;
    width: 100%;
    background-color: #282c34;
    color: #61dafb;
    border: none;
    font-size: 1rem;
    :active {
        background: #0053ba;
    }
    :hover {
        border: 1px solid #cccccc;
        border-radius: 3px;
        cursor: pointer;
    }
`;

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>
                  <Td>{this.props.name}</Td>
                  <Td>{this.props.ticker}</Td>
                  {this.props.showBalance ? <Td>{this.props.balance}</Td> : null}
                  <Td>${this.props.price}</Td>
                  <Td>
                      <form action="">
                          <Button onClick={this.handleClick}>Refresh</Button>
                      </form>
                  </Td>
                </tr>
              );
    }
}

Coin.propTypes = {
    name: PropTypes.string.isRequired,
    ticker: PropTypes.string.isRequired,
    price: PropTypes.number.isRequired
}
1 Like
App.js handleRefresh fragment
  handleRefresh = async (coinId) => {
    const coin = await axios.get(`https://api.coinpaprika.com/v1/tickers/${coinId}`);
    const newCoinData = this.state.coinData.map((values) => {
      let newValues = { ...values };
      if (coinId === values.key) {
          newValues.price = parseFloat(Number(coin.data.quotes.USD.price).toFixed(4));
      }
      return newValues;
    });
    this.setState({ coinData: newCoinData });
  }
1 Like

so many talented people in here! looking over some of the code others came up with and I hope to be as good with mine some day! I feel so blessed and fortunate to have discovered Ivan On Tech!

1 Like

I modified the code so refresh goes out to coin geko and gets the latest price. The code works but I dont like the code. I got lost in the this, this.protoype and bind and was constantly having my routines and variables being undefined at runtime, but finally got it running. Does any one have opinion on this code?, how should I do it better and cleaner?

import React, { Component } from 'react'
 import './Coin.css';
 import PropTypes from 'prop-types';

 const coingeckoService = require('../../services/coingeko-service');
 
 export default class Coin extends Component {
     constructor(props) {
         super(props);
         this.state = {
             price: this.props.price
         }

         // tricky dicky sht. the following statement is the way we connect the member function
         // handleClick() to the class.
         this.handleClick = this.handleClick.bind(this); 
     }
     
     
     render() {
         return (
            <tr className="coin-row">
                <td>{this.props.name}</td>
                <td>{this.props.ticker}</td>
                <td>${this.state.price}</td>
                <td>
                    <button onClick={this.handleClick}>Refresh</button>
                </td>
            </tr>
         )
     }

     /**
      * Come here when you need to process a mouse click event on the refresh coin button.
      * @param {*} event 
      */
     handleClick(event) {
        event.preventDefault();
        console.log("this.props.ticker=", this.props.ticker);
        
        this.getPriceQuoteFromCoingeko( this, this.props.ticker, (response)=> {
            // kind of a useless call back function here.
            console.log("returned from getting data from geko and updating it");
        });
     }
     
 }  //endCoin

 // data member requirements and type.
 Coin.protoType = {
     name: PropTypes.string.isRequired,
     ticker: PropTypes.string.isRequired,
     price: PropTypes.number.isRequired
 }

 /**
  * get all the prices from geko service. 
  * @param {*} callback 
  */
 Coin.prototype.getPricesFromCoingeko = function (callback) {
    console.log("getting prices");
   
    coingeckoService.getPriceQuotes((response) => {
        console.log("geko response=", response);  
        callback(response);
    })
    
 }

 /**
  * when mouse click we come here to get a fresh quote from geko for a particular coin.
  * then set the state for that coin.
  * 
  * @param {*} thisThing a pointer to the coin object so I can update the state
  * @param {*} ticker       
  * @param {*} callback     call this with the results.
  */
 Coin.prototype.getPriceQuoteFromCoingeko = function (thisThing, ticker, callback) {
    console.log("getting price for ", ticker);

    let gekocoinid = coingeckoService.getTickerFromName(ticker);
    console.log( "gekoid=", gekocoinid);
   
    console.log("what is thisThing", thisThing);  
    coingeckoService.getPriceQuote(gekocoinid, (response) => {
        console.log("maded it back from geko call",response);
        console.log(response.currency, response.usd);
        console.log("what is thisThing", thisThing);  
        thisThing.setState( function (oldState) {
            return { 
                price: response.usd
            }
        }); 
        callback(response);
    });
    
 }
1 Like

app.js :

// update the price, leave all the other things the same
handleRefresh = async(valueChangedTicker) => {
const response = await axios.get(tickerURL + valueChangedTicker);
const newCoinPriceData = response.data;
const newCoinData = this.state.coinData.map(function(coin) {
let newCoin = coin;
if ( coin.coinid === valueChangedTicker) {
newCoin.price = newCoinPriceData.quotes.USD.price;
console.log("refresh ", valueChangedTicker, “new price=”, newCoin.price);
}
return newCoin;
});
this.setState({coinData: newCoinData});
}

You can remove the binding you indicated as “tricky dicky sht” if your event handler is an arrow function instead of a simple function reference. Why? Because arrow functions auto-bind the context.

There are two choices to do this:

  1. Class properties:
handleClick = (event) => { ... }
  1. Creating an arrow function in the binding (less advised)
<button onClick={(e) => this.handleClick(e)}>Refresh</button>
1 Like