Currency exchange web app in ~100 lines using the CRA (Create React App)
Tags: react, tutorial, javascript, webdev
Published: 2020-02-17 16:36:51

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](https://rates.now.sh).

## :hammer_and_wrench: Preparation

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

```bash
$ yarn create react-app rates
```

Run the command in your root

```bash
$ yarn start
```

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


## :anchor: Fetching the currencies rate

For currency exchange data we will use free API on [exchangeratesapi.io](https://exchangeratesapi.io/)

We will load the latest data of the currencies rates.

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

```bash
$ yarn add swr unfetch
```

Create the fetcher function outside the App component

```jsx
// 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...
```

Add **useSWR** in App component to fetch data

```jsx
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>;
 }
```

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](https://api.exchangeratesapi.io/latest?base=EUR)

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

## :lipstick: Add the UI

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

```bash
$ yarn add @material-ui/core
```

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


```jsx
// ...
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>
  );
}
```

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.

```jsx
  <Select>
    <MenuItem value={"EUR"}>EUR</MenuItem>
    {Object.keys(currencies.rates).map((rate, key) => (
      <MenuItem key={key} value={rate}>
        {rate}
      </MenuItem>
    ))}
  </Select>
```
When the currencies are not loaded, we just return null, because of currencies rates is undefined at that moment.

```jsx
  if (!currencies) {
    return null;
  }
```

Add some styles to *App.css* 

```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%;
}
```

## :gear: Add logic

Now we add the inputs state to our *App* component

```jsx
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>
  );
}
```

Also, add the two following functions for currency exchange

```javascript
  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);
  };
```

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.

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

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

Here is a full *App.js* file

```jsx
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;
```

## :sparkles: 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](https://github.com/epranka/rates).

[Live demo](https://rates.now.sh).

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

Follow on [Twitter](https://twitter.com/epranka), [GitHub](https://github.com/epranka), and let’s connect on [LinkedIn](https://linkedin.com/in/epranka)