DEV Community

Cover image for Currency exchange web app in ~100 lines using the CRA (Create React App)
Edvinas Pranka
Edvinas Pranka

Posted on

Currency exchange web app in ~100 lines using the CRA (Create React App)

This tutorial describes how to create a simple currency exchange app in just about 100 lines of code using the Create React App boilerplate.

To see it in action check out this live demo.

πŸ› οΈ Preparation

First, create the react app. I call it "rates".

$ yarn create react-app rates
Enter fullscreen mode Exit fullscreen mode

Run the command in your root

$ yarn start
Enter fullscreen mode Exit fullscreen mode

Your web app will be available on http://localhost:3000

βš“ Fetching the currencies rate

For currency exchange data we will use free API on exchangeratesapi.io

We will load the latest data of the currencies rates.

Install the swr and unfetch libraries. It helps to easily fetch API.

$ yarn add swr unfetch
Enter fullscreen mode Exit fullscreen mode

Create the fetcher function outside the App component

// src/App.js
import React from "react";
import "./App.css";
import fetch from "unfetch";

const API_URL = "https://api.exchangeratesapi.io";

const fetcher = async path => {
  const res = await fetch(API_URL + path);
  const json = await res.json();
  return json;
};

// function App...
Enter fullscreen mode Exit fullscreen mode

Add useSWR in App component to fetch data

import React from "react";
import "./App.css";
import fetch from "unfetch";
import useSWR from "swr";

 // API_URL = ...

 // const fetcher = ...

 function App() {
  const { data: currencies } = useSWR("/latest?base=EUR", fetcher);

  return <div>Welcome to your Currency exchange app!</div>;
 }
Enter fullscreen mode Exit fullscreen mode

Now we have the currencies rates in currencies.rates variable.

To see the JSON data of currencies rates you can open the API url directly into your browser: https://api.exchangeratesapi.io/latest?base=EUR

The ?base=EUR query is used to get all rates relative to EUR currency.

πŸ’„ Add the UI

For ui, we will use the material-ui design system. Install it:

$ yarn add @material-ui/core
Enter fullscreen mode Exit fullscreen mode

Create the UI for our currency exchange app in the App component.

// ...
import {
  Container,
  Paper,
  Grid,
  TextField,
  Select,
  MenuItem
} from "@material-ui/core";

// ...

function App() {
  const { data: currencies } = useSWR("/latest?base=EUR", fetcher);

  if (!currencies) {
    return null;
  }

  return (
    <Container className="currency-exchange-container" fixed>
      <h1>Currency exchange</h1>
      <Paper
        className="currency-exchange-paper"
        variant="outlined"
        elavation={1}
      >
        <Grid container spacing={3}>
          <Grid item xs={6}>
            <TextField type="number" />
          </Grid>
          <Grid item xs={6}>
            <TextField type="number" />
          </Grid>
          <Grid item xs={6}>
            <Select>
              <MenuItem value={"EUR"}>EUR</MenuItem>
              {Object.keys(currencies.rates).map((rate, key) => (
                <MenuItem key={key} value={rate}>
                  {rate}
                </MenuItem>
              ))}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <Select>
              <MenuItem value={"EUR"}>EUR</MenuItem>
              {Object.keys(currencies.rates).map((rate, key) => (
                <MenuItem key={key} value={rate}>
                  {rate}
                </MenuItem>
              ))}
            </Select>
          </Grid>
        </Grid>
      </Paper>
    </Container>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now, look at the following code snippet. We create the currency selection box by iterating over all possible rates from our API data. Since we fetch rates relative to EUR, so we should manually add the EUR item, because it doesn't exist in rates object.

  <Select>
    <MenuItem value={"EUR"}>EUR</MenuItem>
    {Object.keys(currencies.rates).map((rate, key) => (
      <MenuItem key={key} value={rate}>
        {rate}
      </MenuItem>
    ))}
  </Select>
Enter fullscreen mode Exit fullscreen mode

When the currencies are not loaded, we just return null, because of currencies rates is undefined at that moment.

  if (!currencies) {
    return null;
  }
Enter fullscreen mode Exit fullscreen mode

Add some styles to App.css

h1 {
  font-weight: 300;
  color: #636363;
  margin-bottom: 3rem;
}

.currency-exchange-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

.currency-exchange-paper {
  max-width: 350px;
  padding: 30px 30px 40px 30px;
}

.MuiInput-root {
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

βš™οΈ Add logic

Now we add the inputs state to our App component

function App() {
  const { data: currencies } = useSWR("/latest?base=EUR", fetcher);

  const [fromValue, setFromValue] = useState(1);
  const [toValue, setToValue] = useState(1);

  const [fromCurrency, setFromCurrency] = useState("EUR");
  const [toCurrency, setToCurrency] = useState("EUR");

  const handleFromCurrencyChange = e => {
    setFromCurrency(e.target.value);
  };

  const handleToCurrencyChange = e => {
    setToCurrency(e.target.value);
  };

  const handleFromValueChange = e => {
    setFromValue(parseFloat(e.target.value));
  };

  const handleToValueChange = e => {
    setToValue(parseFloat(e.target.value));
  };

  if (!currencies) {
    return null;
  }

  return (
    <Container className="currency-exchange-container" fixed>
      <h1>Currency exchange</h1>
      <Paper
        className="currency-exchange-paper"
        variant="outlined"
        elavation={1}
      >
        <Grid container spacing={3}>
          <Grid item xs={6}>
            <TextField
              type="number"
              value={fromValue}
              onChange={handleFromValueChange}
            />
          </Grid>
          <Grid item xs={6}>
            <TextField
              type="number"
              value={toValue}
              onChange={handleToValueChange}
            />
          </Grid>
          <Grid item xs={6}>
            <Select value={fromCurrency} onChange={handleFromCurrencyChange}>
              <MenuItem value={"EUR"}>EUR</MenuItem>
              {Object.keys(currencies.rates).map((rate, key) => (
                <MenuItem key={key} value={rate}>
                  {rate}
                </MenuItem>
              ))}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <Select value={toCurrency} onChange={handleToCurrencyChange}>
              <MenuItem value={"EUR"}>EUR</MenuItem>
              {Object.keys(currencies.rates).map((rate, key) => (
                <MenuItem key={key} value={rate}>
                  {rate}
                </MenuItem>
              ))}
            </Select>
          </Grid>
        </Grid>
      </Paper>
    </Container>
  );
}
Enter fullscreen mode Exit fullscreen mode

Also, add the two following functions for currency exchange

  const convertFromTo = () => {
    const fromRate =
      fromCurrency === "EUR" ? 1 : currencies.rates[fromCurrency];
    const valueInEur = fromValue / fromRate;
    const toRate = toCurrency === "EUR" ? 1 : currencies.rates[toCurrency];
    setToValue(valueInEur * toRate);
  };

  const convertToFrom = () => {
    const toRate = toCurrency === "EUR" ? 1 : currencies.rates[toCurrency];
    const valueInEur = toValue / toRate;
    const fromRate =
      fromCurrency === "EUR" ? 1 : currencies.rates[fromCurrency];
    setFromValue(valueInEur * fromRate);
  };
Enter fullscreen mode Exit fullscreen mode

One function converts currencies forward, and other - backward. In both functions, firstly, we convert currencies to EUR, because all rates we fetched from API are relative to euros.

The last thing is to add the React hooks which run the rates exchange after the input change.

  useEffect(() => {
    convertFromTo();
  }, [fromValue, toCurrency]);

  useEffect(() => {
    convertToFrom();
  }, [toValue, fromCurrency]);
Enter fullscreen mode Exit fullscreen mode

Here is a full App.js file

import React, { useState, useEffect } from "react";
import "./App.css";
import fetch from "unfetch";
import useSWR from "swr";
import {
  Container,
  Paper,
  Grid,
  TextField,
  Select,
  MenuItem
} from "@material-ui/core";

const API_URL = "https://api.exchangeratesapi.io";

const fetcher = async path => {
  const res = await fetch(API_URL + path);
  const json = await res.json();
  return json;
};

function App() {
  const { data: currencies } = useSWR("/latest?base=EUR", fetcher);

  const [fromValue, setFromValue] = useState(1);
  const [toValue, setToValue] = useState(1);

  const [fromCurrency, setFromCurrency] = useState("EUR");
  const [toCurrency, setToCurrency] = useState("EUR");

  const handleFromCurrencyChange = e => {
    setFromCurrency(e.target.value);
  };

  const handleToCurrencyChange = e => {
    setToCurrency(e.target.value);
  };

  const handleFromValueChange = e => {
    setFromValue(parseFloat(e.target.value));
  };

  const handleToValueChange = e => {
    setToValue(parseFloat(e.target.value));
  };

  const convertFromTo = () => {
    const fromRate =
      fromCurrency === "EUR" ? 1 : currencies.rates[fromCurrency];
    const valueInEur = fromValue / fromRate;
    const toRate = toCurrency === "EUR" ? 1 : currencies.rates[toCurrency];
    setToValue(valueInEur * toRate);
  };

  const convertToFrom = () => {
    const toRate = toCurrency === "EUR" ? 1 : currencies.rates[toCurrency];
    const valueInEur = toValue / toRate;
    const fromRate =
      fromCurrency === "EUR" ? 1 : currencies.rates[fromCurrency];
    setFromValue(valueInEur * fromRate);
  };

  useEffect(() => {
    convertFromTo();
  }, [fromValue, toCurrency]);

  useEffect(() => {
    convertToFrom();
  }, [toValue, fromCurrency]);

  if (!currencies) {
    return null;
  }

  return (
    <Container className="currency-exchange-container" fixed>
      <h1>Currency exchange</h1>
      <Paper
        className="currency-exchange-paper"
        variant="outlined"
        elavation={1}
      >
        <Grid container spacing={3}>
          <Grid item xs={6}>
            <TextField
              type="number"
              value={fromValue}
              onChange={handleFromValueChange}
            />
          </Grid>
          <Grid item xs={6}>
            <TextField
              type="number"
              value={toValue}
              onChange={handleToValueChange}
            />
          </Grid>
          <Grid item xs={6}>
            <Select value={fromCurrency} onChange={handleFromCurrencyChange}>
              <MenuItem value={"EUR"}>EUR</MenuItem>
              {Object.keys(currencies.rates).map((rate, key) => (
                <MenuItem key={key} value={rate}>
                  {rate}
                </MenuItem>
              ))}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <Select value={toCurrency} onChange={handleToCurrencyChange}>
              <MenuItem value={"EUR"}>EUR</MenuItem>
              {Object.keys(currencies.rates).map((rate, key) => (
                <MenuItem key={key} value={rate}>
                  {rate}
                </MenuItem>
              ))}
            </Select>
          </Grid>
        </Grid>
      </Paper>
    </Container>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

✨ Finished!

Congratulations! You have done the currency exchange app using the CRA (Create React App).

The full source code you can find in my repository epranka/rates.

Live demo.

Thanks for reading this. I hope it was helpful to you. Feedback and questions are appreciated.

Follow on Twitter, GitHub, and let’s connect on LinkedIn

Top comments (2)

Collapse
 
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

There seems to be a bug in the app related to rounding errors, where entering something like 121 rounds it off to 120.9999999.....

Collapse
 
epranka profile image
Edvinas Pranka

Thanks for reporting it! I keep in mind this and fix it soon. But I think is not an issue for the tutorial itself :)