DEV Community

Cover image for React - FlatCoin
Danny Rivera
Danny Rivera

Posted on • Updated on

React - FlatCoin

Welcome to React - FlatCoin! This App was develop as a Flatiron School React project. I developed this app to help users practice trading cryptocurrencies using virtual (fake) money.

First thing to mention, this app is an incomplete one. My original plan was to come up with a fully functional trading application but I didn't anticipate multiple tricky variables like calculating true and updated return on total investment and data collected to display charts. But I made sure to meet project requirements, this app is still a work in progress.

Back End Setup:

Rails make it very easy to set up a backend API for a React-Project.

rails new react-flatcoin-rails-backend --api
Enter fullscreen mode Exit fullscreen mode

I generated my models via scaffold:

rails generate scaffold user first_name last_name email password:digest
rails generate scaffold portfolio name description initial_balance:decimal current_balance:decimal user_id:integer
rails generate scaffold trade coin_name coin_id price:decimal quantity:integer user_id:integer portfolio_id:integer
Enter fullscreen mode Exit fullscreen mode

I used serializers to easily control the way I'm displaying data as a JSON.

gem 'active_model_serializers'
Enter fullscreen mode Exit fullscreen mode

And finally I created a session controller to deal with User Authentication.

rails generate controller Session new --no-helper --no-assets --no-test-framework --skip-routes --skip
Enter fullscreen mode Exit fullscreen mode

Check out my back end repository here on GitHub!

Note: You may refer to my previous Rails project for more detailed information on Rails API FlatCoin on Rails.

Front End:

First thing first, it's really easy to create the basic structure of a react App:

npx create-react-app react-flatcoin
Enter fullscreen mode Exit fullscreen mode

Then make sure to add Redux:

npm install react-redux --save
Enter fullscreen mode Exit fullscreen mode

When starting an App from scratch it makes sense to start off by visualizing your final product, it doesn't have to be exactly like your final product but at least have a good idea of the different components of your app. My app required a user logging in and playing around with its portfolio of cryptocurrencies, so for me it make sense to start off by setting up User Authorization.
This part gave me such a hard time, but after 3 days of struggle I found this useful article about its setup Using JWT in Your React+Redux App for Authorization check it out, will save you hours.

My next challenge was to fully understand Middleware and asynchronous actions within React/Redux in order to start creating the functionality of my App, since my app required to fetch data from both my Rails API back end and live cryptocurrency data from CoinGecko. After some heavy research I found this YouTube video that helped me fully understand the whole process.

In index.js I'm using Provider to create a global store to keep things simple, I'm also using combineReducers to combine cryptosReducer and usersReducer since they are being fetch from 2 different back ends.

// React
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// Imporing CSS
import './index.css';

// Redux - Thunk
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'

// Imporing Reducers
import { combineReducers } from 'redux'
import cryptosReducer from './reducers/cryptosReducer'
import usersReducer from './reducers/usersReducer'

import reportWebVitals from './reportWebVitals';

const rootReducer = combineReducers({
  user: usersReducer,
  crypto: cryptosReducer
})

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

// Creating Store
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, logger)))

ReactDOM.render(
  <React.StrictMode>  
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();
Enter fullscreen mode Exit fullscreen mode

logger turned out to be an excellent tool to visualize the previous state and the next step based on the action that's been passed.

npm install react-logger --save
Enter fullscreen mode Exit fullscreen mode

Just add it as a second middleware when creating store:

const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, logger)))
Enter fullscreen mode Exit fullscreen mode

On your console:
Logger
My App.js file is easy to follow, I created a fetchEverything function to deal with fetchLoggedInUser and fetchCryptos via componentDidMount.

I handle loading via handleCryptoListLoading's logic, then render returns all its content.

mapStateToProps function maps proper state values and mapDispatchToProps dispatches its values, then I use connect to connect them both:

export default connect(mapStateToProps, mapDispatchToProps)(App)
Enter fullscreen mode Exit fullscreen mode

Full code:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchCryptos } from './actions/cryptoActions'
import { fetchLoggedInUser } from './actions/userActions'
import { logOutUser } from './actions/userActions'
import CryptoList from './components/CryptoList'
import TradeCryptoList from './components/TradeCryptoList'
import NavBar from './components/NavBar'
import NotLoggedInNavBar from './components/NotLoggedInNavBar'
import Dashboard from './components/Dashboard'
import LoginForm from './containers/loginForm'
import SignUpForm from './containers/signUpForm'
import TradeCryptoHoldingDescription from './components/TradeCryptoDescription'
import TradeCryptoHistoryList from './components/TradeCryptoHistoryList'

import './App.css';

import Loading from './svg/Loading'

import {
  BrowserRouter as Router,
  Switch,
  Route
} from 'react-router-dom'

class App extends Component {

  fetchEverything = () => {
    this.props.fetchLoggedInUser()
    this.props.fetchCryptos()
  }

  componentDidMount() {
    this.fetchEverything()
  }

  handleCryptoListLoading = () => { console.log true or false
    if(this.props.loading) {
      return <Loading />
    } else {
console.log(this.props.current_user.first_name)
      return <CryptoList cryptoData={this.props.cryptoData} />
    }
  }

  handleTradeCryptoListLoading = () => { console.log true or false
    if(this.props.user_loading) {
      return <Loading />
    } else {
      return <TradeCryptoList cryptoData={this.props.cryptoData} current_user={this.props.current_user} />
    }
  }

  handleTradeCryptoHistoryListLoading = () => { console.log true or false
    if(this.props.user_loading) {
      return <Loading />
    } else {
      return <TradeCryptoHistoryList cryptoData={this.props.cryptoData} current_user={this.props.current_user} />
    }
  }

  logOut = () => {
    localStorage.removeItem("token")
    this.props.logOutUser()
    alert("Succesfully log out!")
  }

  render() {
    return (
      <>
        <div className="App">
          <Router>
            <div className="app__navBar">
              {this.props.login? <NavBar logOut = {this.logOut}/> : <NotLoggedInNavBar/> }
            </div>
            <Switch>

              <Route exact path="/">   
                <div className="app__body">
                  <div className="app__container">
                    <Dashboard current_user={this.props.current_user}/>
                    {this.handleCryptoListLoading()}
                  </div>
                </div>
              </Route>

              <Route exact path="/login">
                <LoginForm />
              </Route>

              <Route exact path="/signup">
                <SignUpForm />
              </Route>

              <Route exact path="/logout">
                <h1>Loguot</h1>
              </Route>


              <Route path="/coins/:coin_id" component={(routeInfo) => {
                const paramsCoinId = routeInfo.match.params.coin_id
                const foundCoin = this.props.current_user.coins.find(p=> p.coin_id === paramsCoinId)
                const foundCrypto = this.props.cryptoData.find(p=> p.symbol === paramsCoinId)
                const foundPortfolio = this.props.current_user.portfolios.find(p=> p.name === "Initial Portfolio")
                return <TradeCryptoHoldingDescription coins={foundCoin} current_user={this.props.current_user} crypto={foundCrypto} portfolio={foundPortfolio}/>
              </Route>

              <Route exact path="/trades">               {this.handleTradeCryptoHistoryListLoading()}
              </Route> 

              <Route exact path="/portfolio">
                {this.handleTradeCryptoListLoading()}
              </Route>            

              <Route path="/" render={() => <div><h1>Oops! That page doesn't exist.</h1></div>} />

            </Switch>
          </Router>
        </div>
      </>
    )
  }
}

const mapStateToProps = state => {
  return {
    cryptoData: state.crypto.cryptos,
    loading: state.crypto.loading,
    login: state.user.login,
    current_user: state.user.user,
    user_loading: state.user.loading
  }
}

const mapDispatchToProps = dispatch => {
  return {
    fetchCryptos: () => dispatch(fetchCryptos()),
    fetchLoggedInUser: () => dispatch(fetchLoggedInUser()),
    logOutUser: () => dispatch(logOutUser())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
Enter fullscreen mode Exit fullscreen mode

Actions: when fetching data from CoinGecko I use the ADD_CRYPTOS action:

export const fetchCryptos = () => {
    return (dispatch) => {
        dispatch({ type: 'LOADING_CRYPTOS'})
        fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin%2C%20ethereum%2C%20binancecoin%2C%20tether%2C%20polkadot%2C%20cardano%2C%20ripple%2C%20litecoin%2C%20chainlink%2C%20bitcoin-cash%2C%20stellar%2C%20usd-coin%2C%20uniswap%2C%20dogecoin%2C%20wrapped-bitcoin%2C%20okb%2C%20aave%2C%20cosmos%2C%20nem%2C%20solana%20%20&order=market_cap_desc&per_page=100&page=1&sparkline=false&price_change_percentage=1h')
        .then(response => {
            return response.json()
        })
        .then(responseJSON => {
            dispatch({ type: 'ADD_CRYPTOS', cryptos: responseJSON})
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Reducer:

const cryptosReducer = (state = {
    cryptos: [],
    loading:false
}, action) => {
    switch(action.type) {
        case 'LOADING_CRYPTOS':
            return {
                ...state,
                cryptos: [...state.cryptos],
                loading: true
            }
        case 'ADD_CRYPTOS':
            return {
                ...state,
                cryptos: action.cryptos,
                loading: false
            }
        default:
            return state
    }
}

export default cryptosReducer
Enter fullscreen mode Exit fullscreen mode

I used a very similar approach to fetch user's data from my Rails API back end, you may check out front end repository here on GitHub for full code.
Check out a quick YouTube video of my application.

I went through a lot of challenges when building up this App, but it gave me a clear understanding of React. Now I feel ready to keep on refining this project or start another from scratch and avoid those mistakes I made with this one.

Latest comments (0)