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.

  1. 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 the amount prop passed down to the InputBox, causing the input field to reflect the new value.

  2. 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 the selectCurrency prop passed down to the InputBox, 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 takes amount as a parameter and calls setAmount with that amount.

  • When the input changes: The event handler (onChange in the input element) calls onAmountChange with the new value of the input (converted to a number). This new value (amount) is then passed to the setAmount 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 calls setAmount with the current value of amount.

  • When the input changes: The event handler (onChange in the input element) calls onAmountChange. However, because onAmountChange does not take the new input value as a parameter, it calls setAmount with the current value of amount 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 and to 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:

  1. 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.

  2. 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.

  3. 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:

  1. Hooks to Components:

    • useCurrencyInfo fetches the exchange rates and stores them in its state.

    • The App component uses useCurrencyInfo to get the exchange rates and passes them to the InputBox component as props.

  2. Components to Hooks:

    • When the user changes the amount or selects a different currency, event handlers in InputBox call functions like setAmount or setFrom provided by the App 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.


0
Subscribe to my newsletter

Read articles from Rudraksh Tripathi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rudraksh Tripathi
Rudraksh Tripathi