Building a Currency converter using React
Learning to create a custom hook
import {useEffect, useState} from "react"
function useCurrencyInfo(currency){
const [data, setData] = useState({})
useEffect(() => {
fetch(`https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/${currency}.json`)
.then((res) => res.json())
.then((res) => setData(res[currency]))
console.log(data);
}, [currency])
console.log(data);
return data
}
export default useCurrencyInfo;
In my currency converter project, I created a custom hook named useCurrencyInfo
to fetch currency data from an API. In the useEffect
hook, I called the currency API using fetch. This returns a promise, and to handle this promise, I used .then(). The data returned by the promise is in a stream format, so res.json() converts it into JSON. The useCurrencyInfo
hook returns the data, which is the list of currencies fetched from the API call.
Creating this custom hook helped encapsulate the API fetching logic, making it reusable across different components and separating the concerns of data fetching and component rendering.
Hooks in general, such as useState
and useEffect
, simplify code, promote the use of functional components, and allow for reusability and encapsulation of logic. This makes React code cleaner, more readable, and easier to maintain. They also help in UI updation.
Hooks ensure that the UI updates properly. For example, when the page is refreshed, hooks render the default values of the variables visible on the UI. Similarly, when there is user interaction with the UI that changes the variable values, hooks ensure that these new variable values are rendered and successfully updated in the UI.
import React, {useId} from 'react'
function InputBox({
label,
amount,
onAmountChange,
onCurrencyChange,
currencyOptions = [],
selectCurrency = "usd",
amountDisable = false,
currencyDisable = false,
className = "",
}) {
const amountInputId = useId()
return (
<div className={`bg-white p-3 rounded-lg text-sm flex ${className}`}>
<div className="w-1/2">
<label htmlFor={amountInputId} className="text-black/40 mb-2 inline-block">
{label}
</label>
<input
id={amountInputId}
className="outline-none w-full bg-transparent py-1.5"
type="number"
placeholder="Amount"
disabled={amountDisable}
value={amount}
onChange={(e) => onAmountChange && onAmountChange(Number(e.target.value))}
/>
</div>
<div className="w-1/2 flex flex-wrap justify-end text-right">
<p className="text-black/40 mb-2 w-full">Currency Type</p>
<select
className="rounded-lg px-1 py-1 bg-gray-100 cursor-pointer outline-none"
value={selectCurrency}
onChange={(e) => onCurrencyChange && onCurrencyChange(e.target.value)}
disabled={currencyDisable}
>
{currencyOptions.map((currency) => (
<option key={currency} value={currency}>
{currency}
</option>
))}
</select>
</div>
</div>
);
}
export default InputBox;
Next we are going to talk about the component that i built. If you look at the project you will notice that currency from and to components are very similar, so there is no point of writing them twice, that i why i created a component. The component will take different prop values for currency from and to components.
The prop values were label, amount etc. The amount value is also sent as a prop value to the component.
Input Element:
The
onChange
event is triggered.The
onAmountChange
function is called with the new value which came into the component as a prop.This updates the state in the parent component (
App
), which in turn updates theamount
prop passed down to theInputBox
, causing the input field to reflect the new value.
Select Element:
The
onChange
event is triggered.The
onCurrencyChange
function is called with the new value.This updates the state in the parent component (
App
), which in turn updates theselectCurrency
prop passed down to theInputBox
, causing the selected option to change.
This mechanism ensures that the InputBox
component can update its display based on user interactions and propagate those changes back up to the parent component.
Handling onChange Events in Input Elements
While working on the currency converter project, I encountered a scenario where I needed to handle onChange
events for input elements. Here’s how I managed it and the insights I gained:
The Scenario
In the InputBox
component, we have an input element for entering amounts and a select element for choosing currency types. Here's a snippet of the code:
<input
id={amountInputId}
className="outline-none w-full bg-transparent py-1.5"
type="number"
placeholder="Amount"
disabled={amountDisable}
value={amount}
onChange={(e) => onAmountChange && onAmountChange(Number(e.target.value))}
/>
And in the App
component, we pass the onAmountChange
function as a prop:
<InputBox
label="From"
amount={amount}
currencyOptions={options}
onCurrencyChange={(currency) => setAmount(amount)}
selectCurrency={from}
onAmountChange={(amount) => setAmount(amount)}
/>
The Question
Why do we need to use the following form to handle the onChange
event?
onAmountChange={(amount) => setAmount(amount)}
Why can't we just do this?
onAmountChange={() => setAmount(amount)}
Explanation
Current Implementation:
onAmountChange={(amount) => setAmount(amount)}
onAmountChange
: This is the event handler function that will be called when the input value changes.(amount) => setAmount(amount)
: This function takesamount
as a parameter and callssetAmount
with thatamount
.When the input changes: The event handler (
onChange
in the input element) callsonAmountChange
with the new value of the input (converted to a number). This new value (amount
) is then passed to thesetAmount
function to update the state.
Incorrect Implementation:
onAmountChange={() => setAmount(amount)}
onAmountChange
: This is still the event handler function.() => setAmount(amount)
: This function does not take any parameters and callssetAmount
with the current value ofamount
.When the input changes: The event handler (
onChange
in the input element) callsonAmountChange
. However, becauseonAmountChange
does not take the new input value as a parameter, it callssetAmount
with the current value ofamount
from the component's closure, not the new value that the user just input. Therefore, the state does not get updated with the new input value.
Key Difference
The correct implementation properly receives the new value of the input (
amount
) and updates the state with this new value.The incorrect implementation does not receive the new value of the input and instead tries to update the state with the old value of
amount
that was captured in the closure when the function was created.
Main Application Component: App.jsx
In the App
component, I created the core functionality of the currency converter. This component handles the main logic and user interactions for converting currencies. Here’s a detailed breakdown:
State Management
First, I set up the necessary state variables using the useState
hook to manage the component's state.
const [amount, setAmount] = useState(0)
const [from, setFrom] = useState("usd")
const [to, setTo] = useState("inr")
const [convertedAmount, setConvertedAmount] = useState(0)
amount
: The amount of currency to convert.from
: The currency type to convert from.to
: The currency type to convert to.convertedAmount
: The resulting converted amount.
Fetching Currency Information
I used the custom hook useCurrencyInfo
to fetch the currency data based on the selected from
currency.
const currencyInfo = useCurrencyInfo(from)
This hook returns an object containing the exchange rates for the selected currency.
Currency Options
I generated the options for the currency select dropdowns dynamically from the fetched currency data.
const options = Object.keys(currencyInfo)
Swap Functionality
I implemented a swap function to swap the from
and to
currencies, along with their respective amounts.
const swap = () => {
setFrom(to)
setTo(from)
setConvertedAmount(amount)
setAmount(convertedAmount)
}
This function swaps the values of the from
and to
currencies and their respective amounts.
Convert Functionality
I implemented the convert function to calculate the converted amount based on the selected currencies and amount.
const convert = () => {
setConvertedAmount(amount * currencyInfo[to])
}
This function multiplies the amount
by the exchange rate for the to
currency and updates the convertedAmount
state.
Rendering the Component
I used the InputBox
component twice, once for the from
currency and once for the to
currency. The InputBox
component handles user input and displays the available currency options.
<form onSubmit={(e) => {
e.preventDefault();
convert()
}}>
<div className="w-full mb-1">
<InputBox
label="From"
amount={amount}
currencyOptions={options}
onCurrencyChange={(currency) => setAmount(amount)}
selectCurrency={from}
onAmountChange={(amount) => setAmount(amount)}
/>
</div>
<div className="relative w-full h-0.5">
<button
type="button"
className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 border-2 border-white rounded-md bg-blue-600 text-white px-2 py-0.5"
onClick={swap}
>
swap
</button>
</div>
<div className="w-full mt-1 mb-4">
<InputBox
label="To"
amount={convertedAmount}
currencyOptions={options}
onCurrencyChange={(currency) => setTo(currency)}
selectCurrency={to}
amountDisable
/>
</div>
<button type="submit" className="w-full bg-blue-600 text-white px-4 py-3 rounded-lg">
Convert {from.toUpperCase()} to {to.toUpperCase()}
</button>
</form>
From InputBox: Allows the user to input the amount and select the
from
currency.Swap Button: Swaps the
from
andto
currencies and their amounts.To InputBox: Displays the converted amount and allows the user to select the
to
currency.Convert Button: Triggers the conversion when clicked.
User Interface
The entire component is wrapped in a styled div
with a background image for a visually appealing UI.
<div className="w-full h-screen flex flex-wrap justify-center items-center bg-cover bg-no-repeat" style={{ backgroundImage: `url('https://images.pexels.com/photos/3532540/pexels-photo-3532540.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2')` }}>
<div className="w-full">
<div className="w-full max-w-md mx-auto border border-gray-60 rounded-lg p-5 backdrop-blur-sm bg-white/30">
{/* Form content goes here */}
</div>
</div>
</div>
Conclusion
By breaking down the App
component and understanding how it manages state, fetches data, and handles user interactions, I was able to create a functional and user-friendly currency converter. This project helped solidify my understanding of React hooks, state management, and component composition.
Your understanding is correct and provides a clear picture of how the components, hooks, and UI interact with each other in a React application. Here's a refined version of your explanation to add to your article:
Understanding the Flow: UI ↔ Hooks ↔ Components
In this project, the interaction between the UI, hooks, and components can be understood in the following way:
Hooks and UI:
Hooks manage the state and side effects in the application.
The state managed by hooks is reflected in the UI through the components.
When a hook's state variable changes, the UI updates automatically to reflect these changes.
User Interaction with Components:
Users interact with the UI elements provided by components (e.g., input fields, buttons).
These components use props to receive state and functions from hooks.
Data Flow:
From Hooks to Components:
Hooks provide state variables and functions to components via props.
Components render these state variables and provide UI elements for user interaction.
From Components to Hooks:
When users interact with the components (e.g., entering a value, selecting an option), event handlers trigger functions that update the state in the hooks.
These state updates in hooks, in turn, cause the UI to re-render with the new values.
Example in the Currency Converter
In our currency converter:
Hooks: The
useCurrencyInfo
hook fetches currency data and manages the state for currency exchange rates.Components: The
InputBox
component displays the input fields and dropdowns for selecting currencies and entering amounts.UI Update: The
App
component ties everything together, ensuring that changes in the state (managed by hooks) are reflected in the UI (managed by components), and user interactions update the state accordingly.
How it Works:
Hooks to Components:
useCurrencyInfo
fetches the exchange rates and stores them in its state.The
App
component usesuseCurrencyInfo
to get the exchange rates and passes them to theInputBox
component as props.
Components to Hooks:
When the user changes the amount or selects a different currency, event handlers in
InputBox
call functions likesetAmount
orsetFrom
provided by theApp
component.These functions update the state managed by hooks, which triggers a re-render of the components with the new state values.
Visual Representation
UI ↔ Components ↔ Hooks
| |
|----- Data Flow -----|
User interacts with UI elements (components):
Changes in input fields or dropdowns trigger event handlers.
Event handlers call functions provided via props.
Functions update the state in hooks:
- Hooks manage the state and side effects (e.g., fetching data).
State changes trigger UI updates:
- Components re-render with updated state values provided by hooks.
By understanding this flow, we can see how hooks, components, and the UI work together to create a dynamic and responsive application.
Subscribe to my newsletter
Read articles from Rudraksh Tripathi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by