Build a Next.js Interactive Web-Based Currency Converter

Written by

in

In today’s interconnected world, the ability to quickly and accurately convert currencies is more important than ever. Whether you’re planning a trip abroad, managing international finances, or simply curious about the exchange rates, a currency converter is an invaluable tool. In this tutorial, we’ll build a fully functional, interactive currency converter using Next.js, a powerful React framework that makes web development a breeze. This project will not only teach you the fundamentals of Next.js but also provide practical experience in fetching data from APIs, handling user input, and creating a user-friendly interface. By the end of this guide, you’ll have a solid understanding of how to build dynamic web applications and a valuable tool you can use every day.

What We’ll Build

We’re going to create a web-based currency converter that allows users to:

  • Select a base currency and a target currency.
  • Enter an amount to convert.
  • See the converted amount in real-time.
  • Display the latest exchange rates.

This project will utilize Next.js for its server-side rendering and static site generation capabilities, making it performant and SEO-friendly. We’ll also be using an external API to fetch the latest currency exchange rates. Let’s get started!

Prerequisites

Before we begin, you’ll need the following:

  • Node.js and npm (or yarn) installed on your machine.
  • A basic understanding of HTML, CSS, and JavaScript.
  • A code editor (like VS Code, Sublime Text, or Atom).

Setting Up the Next.js Project

First, let’s create a new Next.js project. Open your terminal and run the following command:

npx create-next-app currency-converter
cd currency-converter

This command creates a new Next.js project named “currency-converter” and navigates you into the project directory. Next.js sets up a basic project structure for you, including:

  • pages/: This directory is where you’ll create your pages. Each file in this directory becomes a route.
  • public/: This directory is for static assets like images, fonts, and other files.
  • styles/: This directory is for your CSS or other styling files.
  • package.json: This file lists your project dependencies and scripts.

Installing Dependencies

For this project, we’ll need to install a couple of dependencies:

  • axios: A promise-based HTTP client for making API requests.
  • react-select: A customizable select input component for selecting currencies.

Run the following command in your terminal within the project directory:

npm install axios react-select

Fetching Currency Exchange Rates

We need a reliable source for real-time currency exchange rates. There are several free and paid APIs available. For this tutorial, we’ll use a free API. You can find many options by searching online for “free currency exchange rate API”. Remember to check the API’s terms of service and usage limits. For the purpose of this example, we’ll assume the API provides an endpoint like this:

https://api.example.com/latest?base=[BASE_CURRENCY]&symbols=[TARGET_CURRENCY]

This API would return a JSON response containing the exchange rates. Here’s a simplified example of what the response might look like:

{
 "rates": {
  "USD": 1.0, // Base currency is USD
  "EUR": 0.85,
  "GBP": 0.73
 },
 "base": "USD",
 "date": "2024-01-01"
}

Now, let’s create a function to fetch these rates. Create a new file called utils/api.js in your project’s root directory. Inside this file, add the following code:

import axios from 'axios';

const API_KEY = 'YOUR_API_KEY'; // Replace with your actual API key if needed
const API_URL = 'https://api.exchangerate-api.com/v4/latest'; // Replace with your API endpoint

async function getExchangeRates(baseCurrency, targetCurrency) {
  try {
    const params = {
      base: baseCurrency,
      symbols: targetCurrency
    }
    const response = await axios.get(API_URL, { params });
    return response.data;
  } catch (error) {
    console.error('Error fetching exchange rates:', error);
    throw error;
  }
}

export { getExchangeRates };

Remember to replace 'YOUR_API_KEY' and API_URL with your actual API key and endpoint. This function uses axios to make a GET request to the API and returns the data. It also includes error handling to catch and log any issues that may arise during the API call.

Building the Currency Converter Component

Now, let’s build the core component of our currency converter. Open the pages/index.js file (this is the main page of your application) and replace its content with the following:

import { useState, useEffect } from 'react';
import Select from 'react-select';
import { getExchangeRates } from '../utils/api';

const currencyOptions = [
  { value: 'USD', label: 'USD - US Dollar' },
  { value: 'EUR', label: 'EUR - Euro' },
  { value: 'GBP', label: 'GBP - British Pound' },
  { value: 'JPY', label: 'JPY - Japanese Yen' },
  { value: 'CAD', label: 'CAD - Canadian Dollar' },
  // Add more currencies here
];

export default function Home() {
  const [fromCurrency, setFromCurrency] = useState('USD');
  const [toCurrency, setToCurrency] = useState('EUR');
  const [amount, setAmount] = useState(1);
  const [convertedAmount, setConvertedAmount] = useState(null);
  const [exchangeRate, setExchangeRate] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const convertCurrency = async () => {
      setIsLoading(true);
      setError(null);
      try {
        const data = await getExchangeRates(fromCurrency, toCurrency);
        if (data && data.rates && data.rates[toCurrency]) {
          const rate = data.rates[toCurrency];
          setExchangeRate(rate);
          setConvertedAmount(amount * rate);
        } else {
          setError('Exchange rate not found.');
          setConvertedAmount(null);
          setExchangeRate(null);
        }
      } catch (error) {
        setError('Failed to fetch exchange rates.');
        setConvertedAmount(null);
        setExchangeRate(null);
      } finally {
        setIsLoading(false);
      }
    };

    convertCurrency();
  }, [fromCurrency, toCurrency, amount]);

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

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

  const handleAmountChange = (e) => {
    const value = parseFloat(e.target.value);
    setAmount(isNaN(value) ? 0 : value);
  };

  return (
    <div>
      <h1>Currency Converter</h1>
      {error && <p>{error}</p>}
      <div>
        <label>Amount:</label>
        
      </div>
      <div>
        <label>From:</label>
         option.value === fromCurrency)}
          onChange={handleFromCurrencyChange}
          options={currencyOptions}
        />
      </div>
      <div>
        <label>To:</label>
         option.value === toCurrency)}
          onChange={handleToCurrencyChange}
          options={currencyOptions}
        />
      </div>
      {isLoading && <p>Loading...</p>}
      {convertedAmount !== null && !isLoading && (
        <p>Converted Amount: {convertedAmount.toFixed(2)}</p>
      )}
      {exchangeRate !== null && !isLoading && (
        <p>Exchange Rate: 1 {fromCurrency} = {exchangeRate.toFixed(4)} {toCurrency}</p>
      )}
      {`
        .container {
          display: flex;
          flex-direction: column;
          align-items: center;
          padding: 20px;
          font-family: sans-serif;
        }
        h1 {
          margin-bottom: 20px;
        }
        .input-group, .select-group {
          margin-bottom: 15px;
          display: flex;
          flex-direction: column;
          width: 300px;
        }
        label {
          margin-bottom: 5px;
        }
        input, select {
          padding: 8px;
          border: 1px solid #ccc;
          border-radius: 4px;
          font-size: 16px;
        }
        .error {
          color: red;
          margin-bottom: 10px;
        }
      `}
    </div>
  );
}

Let’s break down this code:

  • Import Statements: We import the necessary modules: useState and useEffect from React, Select from react-select, and getExchangeRates from our utils/api.js file.
  • currencyOptions: This array holds the available currency options for the select dropdowns. You can easily add more currencies to this array.
  • State Variables: We use the useState hook to manage the component’s state. These variables store the selected currencies (fromCurrency, toCurrency), the amount to convert (amount), the converted amount (convertedAmount), the exchange rate (exchangeRate), a loading state (isLoading), and any potential errors (error).
  • useEffect Hook: This hook is used to fetch the exchange rates whenever the fromCurrency, toCurrency, or amount changes. Inside the hook, we call the getExchangeRates function, update the convertedAmount, and handle loading and error states.
  • Event Handlers: We have event handlers (handleFromCurrencyChange, handleToCurrencyChange, handleAmountChange) to update the state when the user interacts with the input fields and select dropdowns.
  • JSX Structure: The return statement contains the JSX that renders the UI. It includes:

    • An input field for the amount.
    • Two Select components (using react-select) for selecting the currencies.
    • Conditional rendering for the loading state and the converted amount.
  • Inline Styles: Basic CSS styles are included using the jsx syntax to make the component look presentable. You can customize these styles further in a separate CSS file for better organization.

Styling the Application

While the inline styles in the previous code snippet are functional, let’s improve the styling by creating a separate CSS file. Create a file named styles/Home.module.css in your project’s styles directory. Add the following CSS rules:

.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  font-family: sans-serif;
  max-width: 600px;
  margin: 0 auto;
}

h1 {
  margin-bottom: 20px;
}

.input-group, .select-group {
  margin-bottom: 15px;
  display: flex;
  flex-direction: column;
  width: 100%;
}

label {
  margin-bottom: 5px;
  font-weight: bold;
}

input, .select-container {
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 16px;
  margin-bottom: 10px;
}

.error {
  color: red;
  margin-bottom: 10px;
}

.loading {
  margin-top: 10px;
  font-style: italic;
}

.converted-amount {
  font-size: 1.2em;
  font-weight: bold;
  margin-top: 20px;
}

@media (min-width: 768px) {
  .input-group, .select-group {
    width: 400px;
  }
}

Now, import this CSS file into your pages/index.js file and apply the styles:

import { useState, useEffect } from 'react';
import Select from 'react-select';
import { getExchangeRates } from '../utils/api';
import styles from '../styles/Home.module.css'; // Import the CSS file

const currencyOptions = [
  { value: 'USD', label: 'USD - US Dollar' },
  { value: 'EUR', label: 'EUR - Euro' },
  { value: 'GBP', label: 'GBP - British Pound' },
  { value: 'JPY', label: 'JPY - Japanese Yen' },
  { value: 'CAD', label: 'CAD - Canadian Dollar' },
  // Add more currencies here
];

export default function Home() {
  const [fromCurrency, setFromCurrency] = useState('USD');
  const [toCurrency, setToCurrency] = useState('EUR');
  const [amount, setAmount] = useState(1);
  const [convertedAmount, setConvertedAmount] = useState(null);
  const [exchangeRate, setExchangeRate] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const convertCurrency = async () => {
      setIsLoading(true);
      setError(null);
      try {
        const data = await getExchangeRates(fromCurrency, toCurrency);
        if (data && data.rates && data.rates[toCurrency]) {
          const rate = data.rates[toCurrency];
          setExchangeRate(rate);
          setConvertedAmount(amount * rate);
        } else {
          setError('Exchange rate not found.');
          setConvertedAmount(null);
          setExchangeRate(null);
        }
      } catch (error) {
        setError('Failed to fetch exchange rates.');
        setConvertedAmount(null);
        setExchangeRate(null);
      } finally {
        setIsLoading(false);
      }
    };

    convertCurrency();
  }, [fromCurrency, toCurrency, amount]);

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

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

  const handleAmountChange = (e) => {
    const value = parseFloat(e.target.value);
    setAmount(isNaN(value) ? 0 : value);
  };

  return (
    <div>
      <h1>Currency Converter</h1>
      {error && <p>{error}</p>}
      <div>
        <label>Amount:</label>
        
      </div>
      <div>
        <label>From:</label>
         option.value === fromCurrency)}
          onChange={handleFromCurrencyChange}
          options={currencyOptions}
          className={styles.selectContainer}
        />
      </div>
      <div>
        <label>To:</label>
         option.value === toCurrency)}
          onChange={handleToCurrencyChange}
          options={currencyOptions}
          className={styles.selectContainer}
        />
      </div>
      {isLoading && <p>Loading...</p>}
      {convertedAmount !== null && !isLoading && (
        <p>Converted Amount: {convertedAmount.toFixed(2)}</p>
      )}
      {exchangeRate !== null && !isLoading && (
        <p>Exchange Rate: 1 {fromCurrency} = {exchangeRate.toFixed(4)} {toCurrency}</p>
      )}
    </div>
  );
}

By importing the CSS file and using the styles object to apply the classes, we’ve made our code cleaner and more maintainable. This approach is known as CSS Modules, and it’s a recommended practice in Next.js.

Running the Application

To run your application, open your terminal, navigate to your project directory (currency-converter), and run the following command:

npm run dev

This command starts the Next.js development server. Open your web browser and go to http://localhost:3000. You should see your currency converter application running in the browser.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • API Key Issues: Make sure you have a valid API key and that you’ve replaced 'YOUR_API_KEY' with your actual key in the utils/api.js file. Also, check the API’s documentation for any usage limits or rate limits.
  • CORS Errors: If you encounter CORS (Cross-Origin Resource Sharing) errors, it means the API you’re using doesn’t allow requests from your domain. You might need to use a proxy server or configure CORS on the API’s server if you have control over it. For development, you can sometimes use a browser extension that disables CORS, but this is not recommended for production.
  • Incorrect API Endpoint: Double-check the API endpoint URL in utils/api.js to ensure it’s correct. Also, verify that the parameters you are passing (base and symbols) are correctly formatted and accepted by the API.
  • Missing Dependencies: Make sure you have installed all the necessary dependencies (axios and react-select) by running npm install.
  • Incorrect Data Handling: If the converted amount isn’t displaying correctly, check the data returned by the API. Make sure you are accessing the correct properties in the response (e.g., data.rates[toCurrency]) and that you’re handling potential errors gracefully.
  • React-Select Styling: If the react-select component isn’t styled correctly, make sure you’ve included the necessary CSS or have configured the component with your desired styles. You might need to import the default styles provided by react-select or create custom styles.

Adding More Features (Optional)

Here are some ideas to enhance your currency converter:

  • Currency Symbols: Display currency symbols (e.g., $, €, £) next to the converted amounts. You can use a library like currency-symbol-map or manually map currencies to their symbols.
  • Historical Rates: Allow users to view historical exchange rates by adding a date picker and fetching data from the API for specific dates.
  • Error Handling: Implement more robust error handling, such as displaying informative error messages to the user if the API request fails or if the exchange rate is not available.
  • Saving User Preferences: Use local storage or cookies to save the user’s preferred currencies, so they don’t have to select them every time they visit the page.
  • Dark Mode: Implement a dark mode toggle to enhance the user experience.
  • Mobile Responsiveness: Ensure the converter is responsive and looks good on different screen sizes by using CSS media queries.

Key Takeaways

  • You’ve learned how to set up a Next.js project.
  • You’ve integrated an external API to fetch real-time data.
  • You’ve used the useState and useEffect hooks to manage component state and handle side effects.
  • You’ve learned how to create a user-friendly interface with input fields and select dropdowns.
  • You’ve gained practical experience in building a dynamic web application.

FAQ

  1. Can I use a different API? Yes, absolutely! Just replace the API endpoint and adjust the code that parses the API response accordingly. Make sure to check the API’s documentation for its specific requirements.
  2. How can I deploy this application? You can deploy your Next.js application to various platforms, such as Vercel (which is recommended, as it’s the platform created by the Next.js team), Netlify, or AWS. You’ll typically run npm run build to create a production-ready build and then deploy the contents of the .next directory.
  3. Why is server-side rendering important? Server-side rendering (SSR) improves SEO because search engine crawlers can easily index the content of your pages. It also improves the initial load time of your application, as the server pre-renders the HTML.
  4. What is the purpose of the useEffect hook? The useEffect hook is used to handle side effects in your React components. In this case, we use it to fetch data from the API whenever the relevant state variables change.
  5. How can I add more currencies? Simply add more objects to the currencyOptions array in your pages/index.js file, including the currency code and a user-friendly label. Also, make sure the API you are using supports these currencies.

Building this currency converter has provided a hands-on experience in Next.js development, from setting up a project to integrating an external API and creating a user-friendly interface. You’ve learned how to fetch data, handle user input, and manage the application’s state, all crucial skills for any web developer. This project serves as a solid foundation for building more complex web applications using Next.js. Keep experimenting, exploring new features, and refining your skills. The journey of a developer is continuous learning, and with each project, you’ll gain more confidence and expertise. The possibilities are endless, and the only limit is your imagination.