Building a Currency Converter App with Next.js
Day 18: Currency Converter — 30 Days of 30 Projects Challenge
Hey everyone, I hope you’re all doing well and enjoying your coding journey, Am I right? By the way, today marks the eighteenth day of the 30-day of 30-projects challenge. Our 18th project will be creating a mini Next.js application — a currency converter. Please make sure to clap and comment on this blog post. It motivates me to create more amazing content like this, Let’s get started straight away.
Overview of the Mini Next.js Application
Our Currency Converter application allows users to:
- Input for amount and currencies
- Convert and display the result
- Use a currency API (ExchangeRate-API)
Tech-Stack Used:
- Next.js: A React framework for building full-stack web applications.
- React: A JavaScript library for building user interfaces.
- Tailwind CSS: A utility-first CSS framework for styling.
- Shadcn UI: Beautifully designed tailwindcss components that you can copy and paste into your application.
- ExchangeRateAPI: The Accurate & Reliable Currency Exchange Rate API.
- Vercel: For deploying the Nextjs web application.
Initialize a Nextjs project
Start by following this guide to set up the new project.
- Go to the “components” folder.
- Create a new file named “
currency-converter.tsx
”. - This file will manage the entire functionality of the project.
We will go through the code step-by-step to make it easy to understand.
Component Breakdown
Import Statements
"use client";
import { useState, useEffect, ChangeEvent } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectItem } from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
- Enables client-side rendering.
- Imports necessary hooks and custom components for building the UI.
State and Types
type ExchangeRates = {
[key: string]: number;
};
type Currency = "USD" | "EUR" | "GBP" | "JPY" | "AUD" | "CAD" | "PKR";
const [amount, setAmount] = useState<number | null>(null);
const [sourceCurrency, setSourceCurrency] = useState<Currency>("USD");
const [targetCurrency, setTargetCurrency] = useState<Currency>("PKR");
const [exchangeRates, setExchangeRates] = useState<ExchangeRates>({});
const [convertedAmount, setConvertedAmount] = useState<string>("0.00");
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
- Defines the
ExchangeRates
type for currency exchange rates fetched from the API. - Defines the
Currency
type for supported currencies. - Manages state for amount, source and target currencies, exchange rates, converted amount, loading state, and error state.
Fetch Exchange Rates
useEffect(() => {
const fetchExchangeRates = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch("https://api.exchangerate-api.com/v4/latest/USD");
const data = await response.json();
setExchangeRates(data.rates);
} catch (error) {
setError("Error fetching exchange rates.");
} finally {
setLoading(false);
}
};
fetchExchangeRates();
}, []);
- Fetches exchange rates from the ExchangeRateAPI when the component mounts and updates the state.
Input and Select Handlers
const handleAmountChange = (e: ChangeEvent<HTMLInputElement>): void => {
setAmount(parseFloat(e.target.value));
};
const handleSourceCurrencyChange = (value: Currency): void => {
setSourceCurrency(value);
};
const handleTargetCurrencyChange = (value: Currency): void => {
setTargetCurrency(value);
};
- Handles changes in the input field for the amount and the select fields for source and target currencies.
Calculate Converted Amount
const calculateConvertedAmount = (): void => {
if (sourceCurrency && targetCurrency && amount && exchangeRates) {
const rate = sourceCurrency === "USD"
? exchangeRates[targetCurrency]
: exchangeRates[targetCurrency] / exchangeRates[sourceCurrency];
const result = amount * rate;
setConvertedAmount(result.toFixed(2));
}
};
- Calculates the converted amount based on the selected currencies and amount input by the user.
JSX Return Statement
return (
<div className="flex flex-col items-center justify-center h-screen bg-background">
<Card className="w-full max-w-md p-6 space-y-4">
<CardHeader className="text-center">
<CardTitle className="text-2xl font-bold">Currency Converter</CardTitle>
<CardDescription>Convert between different currencies.</CardDescription>
</CardHeader>
<CardContent>
{loading ? (
<div className="flex justify-center">
<Spinner className="w-6 h-6 text-blue-500" />
</div>
) : error ? (
<div className="text-red-500 text-center">{error}</div>
) : (
<div className="grid gap-4">
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<Label htmlFor="from">From</Label>
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<Input
type="number"
placeholder="Amount"
value={amount || ""}
onChange={handleAmountChange}
className="w-full"
id="from"
/>
<Select
value={sourceCurrency}
onValueChange={handleSourceCurrencyChange}
>
<SelectTrigger className="w-24">
<SelectValue placeholder="USD" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="USD">USD</SelectItem>
<SelectItem value="EUR">EUR</SelectItem>
<SelectItem value="GBP">GBP</SelectItem>
<SelectItem value="JPY">JPY</SelectItem>
<SelectItem value="AUD">AUD</SelectItem>
<SelectItem value="CAD">CAD</SelectItem>
<SelectItem value="PKR">PKR</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<Label htmlFor="to">To</Label>
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<div className="text-2xl font-bold">{convertedAmount}</div>
<Select
value={targetCurrency}
onValueChange={handleTargetCurrencyChange}
>
<SelectTrigger className="w-24">
<SelectValue placeholder="EUR" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="USD">USD</SelectItem>
<SelectItem value="EUR">EUR</SelectItem>
<SelectItem value="GBP">GBP</SelectItem>
<SelectItem value="JPY">JPY</SelectItem>
<SelectItem value="AUD">AUD</SelectItem>
<SelectItem value="CAD">CAD</SelectItem>
<SelectItem value="PKR">PKR</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
</div>
)}
</CardContent>
<CardFooter>
<Button
type="button"
className="w-full"
onClick={calculateConvertedAmount}
>
Convert
</Button>
</CardFooter>
</Card>
</div>
);
- Renders the Currency Converter UI with input fields for amount and currency selections, and a button to convert the amount.
(Bonus just for you): Full Code with Comments
"use client"; // Enables client-side rendering for this component
import { useState, useEffect, ChangeEvent } from "react"; // Import React hooks and types
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@/components/ui/card"; // Import custom Card components
import { Label } from "@/components/ui/label"; // Import custom Label component
import { Input } from "@/components/ui/input"; // Import custom Input component
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectGroup,
SelectItem,
} from "@/components/ui/select"; // Import custom Select components
import { Button } from "@/components/ui/button"; // Import custom Button component
import ClipLoader from "react-spinners/ClipLoader";
// Define the ExchangeRates type
type ExchangeRates = {
[key: string]: number;
};
// Define the Currency type
type Currency = "USD" | "EUR" | "GBP" | "JPY" | "AUD" | "CAD" | "PKR";
export default function CurrencyConverter() {
// State to manage the amount input by the user
const [amount, setAmount] = useState<number | null>(null);
// State to manage the source currency selected by the user
const [sourceCurrency, setSourceCurrency] = useState<Currency>("USD");
// State to manage the target currency selected by the user
const [targetCurrency, setTargetCurrency] = useState<Currency>("PKR");
// State to manage the fetched exchange rates
const [exchangeRates, setExchangeRates] = useState<ExchangeRates>({});
// State to manage the converted amount
const [convertedAmount, setConvertedAmount] = useState<string>("0.00");
// State to manage the loading state during data fetch
const [loading, setLoading] = useState<boolean>(false);
// State to manage any error messages
const [error, setError] = useState<string | null>(null);
// useEffect to fetch exchange rates when the component mounts
useEffect(() => {
const fetchExchangeRates = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(
"https://api.exchangerate-api.com/v4/latest/USD"
);
const data = await response.json();
setExchangeRates(data.rates);
} catch (error) {
setError("Error fetching exchange rates.");
} finally {
setLoading(false);
}
};
fetchExchangeRates();
}, []);
// Function to handle changes in the amount input field
const handleAmountChange = (e: ChangeEvent<HTMLInputElement>): void => {
setAmount(parseFloat(e.target.value));
};
// Function to handle changes in the source currency select field
const handleSourceCurrencyChange = (value: Currency): void => {
setSourceCurrency(value);
};
// Function to handle changes in the target currency select field
const handleTargetCurrencyChange = (value: Currency): void => {
setTargetCurrency(value);
};
// Function to calculate the converted amount
const calculateConvertedAmount = (): void => {
if (sourceCurrency && targetCurrency && amount && exchangeRates) {
const rate =
sourceCurrency === "USD"
? exchangeRates[targetCurrency]
: exchangeRates[targetCurrency] / exchangeRates[sourceCurrency];
const result = amount * rate;
setConvertedAmount(result.toFixed(2));
}
};
// JSX return statement rendering the Currency Converter UI
return (
<div className="flex flex-col items-center justify-center h-screen bg-background">
<Card className="w-full max-w-md p-6 space-y-4">
<CardHeader className="text-center">
<CardTitle className="text-2xl font-bold">
Currency Converter
</CardTitle>
<CardDescription>
Convert between different currencies.
</CardDescription>
</CardHeader>
<CardContent>
{loading ? (
<div className="flex justify-center">
<ClipLoader className="w-6 h-6 text-blue-500" />
</div>
) : error ? (
<div className="text-red-500 text-center">{error}</div>
) : (
<div className="grid gap-4">
{/* Amount input and source currency selection */}
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<Label htmlFor="from">From</Label>
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<Input
type="number"
placeholder="Amount"
value={amount || ""}
onChange={handleAmountChange}
className="w-full"
id="from"
/>
<Select
value={sourceCurrency}
onValueChange={handleSourceCurrencyChange}
>
<SelectTrigger className="w-24">
<SelectValue placeholder="USD" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="USD">USD</SelectItem>
<SelectItem value="EUR">EUR</SelectItem>
<SelectItem value="GBP">GBP</SelectItem>
<SelectItem value="JPY">JPY</SelectItem>
<SelectItem value="AUD">AUD</SelectItem>
<SelectItem value="CAD">CAD</SelectItem>
<SelectItem value="PKR">PKR</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
{/* Converted amount display and target currency selection */}
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<Label htmlFor="to">To</Label>
<div className="grid grid-cols-[1fr_auto] items-center gap-2">
<div className="text-2xl font-bold">{convertedAmount}</div>
<Select
value={targetCurrency}
onValueChange={handleTargetCurrencyChange}
>
<SelectTrigger className="w-24">
<SelectValue placeholder="EUR" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="USD">USD</SelectItem>
<SelectItem value="EUR">EUR</SelectItem>
<SelectItem value="GBP">GBP</SelectItem>
<SelectItem value="JPY">JPY</SelectItem>
<SelectItem value="AUD">AUD</SelectItem>
<SelectItem value="CAD">CAD</SelectItem>
<SelectItem value="PKR">PKR</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
</div>
)}
</CardContent>
<CardFooter>
{/* Convert button */}
<Button
type="button"
className="w-full"
onClick={calculateConvertedAmount}
>
Convert
</Button>
</CardFooter>
</Card>
</div>
);
}
Okay, you’ve completed the main component with functional UI. Now, you need to import this component into the app directory to use it in the app/page.tsx
file. Your final code should look like this:
import CurrencyConverter from "@/components/currency-converter";
export default function Home() {
return (
<div>
<CurrencyConverter />
</div>
);
}
Running the Project
To see the currency converter in action, follow these steps:
- Start the Development Server: Run
npm run dev
to start the development server. - Open in Browser: Open
http://localhost:3000
in your browser to view the application.
Make sure to test it properly (each and everything) so that we don’t have any errors in the production mode aka when we host on the internet.
Now, we want people to see our application on the internet. All you have to do is create a repository on GitHub and then push your code to it. After that, deploy the Currency Converter application using Vercel.
Once you’re done with the deployment, please share the application link with me by commenting on this blog post, on Linkedin, and (most importantly, on X a.k.a. Twitter. Tag me there, and I’ll reply and appreciate your efforts) 👀
(Optional): One thing you can do on your own is to add new functionalities, enhance the styling, and improve the overall application. This way, you’ll learn something new by making modifications.
✨ Star Github Repository of the project 👈
Conclusion
In this blog post, we built a Currency Converter application using Next.js. We covered:
- Setting up the project and using client-side rendering.
- Fetching exchange rates from an API and handling user input.
- Converting the amount based on selected currencies.
See you tomorrow with the latest project. Happy coding!
Stay updated with the latest in cutting-edge technology! Follow me:
- Twitter: @0xAsharib
- LinkedIn: Asharib Ali
- GitHub: AsharibAli
- Website: asharib.xyz
Thanks for reading!