Como Construir um Conversor de Moedas com React e uma API de Taxas de Câmbio em Tempo Real
Construir um conversor de moedas com React é um excelente projeto para desenvolvedores que querem praticar integração de APIs, React hooks e gerenciamento de estado em aplicações reais. Neste tutorial passo a passo, você vai construir um conversor de moedas totalmente funcional que busca taxas de câmbio em tempo real de uma API de câmbio, suporta mais de 170 moedas e trata erros de forma elegante — tudo usando padrões modernos do React.
Se você é novo no assunto e quer entender como as taxas de câmbio funcionam, confira nosso explicador de taxas de câmbio antes de começar.
O Que Você Vai Construir
Ao final deste tutorial, você terá um conversor de moedas em React que:
- Busca taxas de câmbio em tempo real da API Finexly
- Suporta mais de 170 moedas via menu suspenso
- Converte valores em tempo real enquanto o usuário digita (com debouncing)
- Trata estados de carregamento e erros de API de forma elegante
- Exibe a taxa e o timestamp da última atualização
Estrutura do projeto:
currency-converter/
├── src/
│ ├── components/
│ │ └── CurrencyConverter.tsx
│ ├── hooks/
│ │ └── useExchangeRate.ts
│ ├── App.tsx
│ └── main.tsx
├── package.json
└── vite.config.tsPré-requisitos
Antes de começar, certifique-se de ter:
- Node.js 18+ instalado
- Familiaridade básica com React hooks (
useState,useEffect) - Uma chave de API gratuita do Finexly (leva cerca de 30 segundos para obter)
Passo 1: Configure Seu Projeto React
Usaremos o Vite como ferramenta de build:
npm create vite@latest currency-converter -- --template react-ts
cd currency-converter
npm install
npm run devSeu servidor de desenvolvimento iniciará em http://localhost:5173.
Passo 2: Entenda a API Finexly
A API Finexly fornece taxas de câmbio em tempo real e históricas para mais de 170 moedas via uma interface REST simples:
GET https://finexly.com/api/v1/latest?base=USDUma resposta típica:
{
"base": "USD",
"date": "2026-04-07",
"rates": {
"EUR": 0.9182,
"GBP": 0.7871,
"JPY": 151.42
},
"timestamp": 1744012800
}Inclua sua chave no cabeçalho da requisição:
Authorization: Bearer SUA_CHAVE_APIVocê pode se registrar para um plano gratuito com 1.000 requisições por mês.
Passo 3: Crie o Hook Personalizado
Crie o arquivo src/hooks/useExchangeRate.ts:
import { useState, useEffect, useCallback } from "react";
interface ExchangeRateResult {
rates: Record<string, number> | null;
loading: boolean;
error: string | null;
lastUpdated: Date | null;
}
const API_KEY = import.meta.env.VITE_FINEXLY_API_KEY;
const BASE_URL = "https://finexly.com/api/v1";
const rateCache: Record<string, { rates: Record<string, number>; timestamp: number }> = {};
const CACHE_TTL_MS = 5 * 60 * 1000;
export function useExchangeRate(baseCurrency: string): ExchangeRateResult {
const [rates, setRates] = useState<Record<string, number> | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const fetchRates = useCallback(async (base: string) => {
const cached = rateCache[base];
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
setRates(cached.rates);
setLastUpdated(new Date(cached.timestamp));
return;
}
setLoading(true);
setError(null);
try {
const res = await fetch(`${BASE_URL}/latest?base=${base}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) throw new Error(`Erro da API: ${res.status} ${res.statusText}`);
const data = await res.json();
rateCache[base] = { rates: data.rates, timestamp: Date.now() };
setRates(data.rates);
setLastUpdated(new Date());
} catch (err) {
setError(err instanceof Error ? err.message : "Falha ao buscar taxas de câmbio");
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if (baseCurrency) fetchRates(baseCurrency);
}, [baseCurrency, fetchRates]);
return { rates, loading, error, lastUpdated };
}Passo 4: Construa o Componente CurrencyConverter
Crie src/components/CurrencyConverter.tsx:
import { useState, useMemo } from "react";
import { useExchangeRate } from "../hooks/useExchangeRate";
const CURRENCIES = [
"USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "CNY",
"INR", "MXN", "BRL", "KRW", "SGD", "HKD", "NOK", "SEK",
];
export default function CurrencyConverter() {
const [amount, setAmount] = useState<string>("100");
const [baseCurrency, setBaseCurrency] = useState<string>("USD");
const [targetCurrency, setTargetCurrency] = useState<string>("EUR");
const { rates, loading, error, lastUpdated } = useExchangeRate(baseCurrency);
const convertedAmount = useMemo(() => {
if (!rates || !amount) return null;
const numAmount = parseFloat(amount);
if (isNaN(numAmount) || numAmount < 0) return null;
const rate = rates[targetCurrency];
return rate ? (numAmount * rate).toFixed(2) : null;
}, [rates, amount, targetCurrency]);
const handleSwap = () => {
setBaseCurrency(targetCurrency);
setTargetCurrency(baseCurrency);
};
return (
<div className="converter-card">
<h2>Conversor de Moedas em Tempo Real</h2>
<div className="input-row">
<label htmlFor="amount">Valor</label>
<input
id="amount"
type="number"
min="0"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Digite o valor"
/>
</div>
<div className="currency-row">
<div className="select-group">
<label htmlFor="base">De</label>
<select id="base" value={baseCurrency} onChange={(e) => setBaseCurrency(e.target.value)}>
{CURRENCIES.map((c) => <option key={c} value={c}>{c}</option>)}
</select>
</div>
<button className="swap-btn" onClick={handleSwap} aria-label="Trocar moedas">⇄</button>
<div className="select-group">
<label htmlFor="target">Para</label>
<select id="target" value={targetCurrency} onChange={(e) => setTargetCurrency(e.target.value)}>
{CURRENCIES.map((c) => <option key={c} value={c}>{c}</option>)}
</select>
</div>
</div>
<div className="result-area">
{loading && <p>Buscando taxas mais recentes...</p>}
{error && <p className="error-text">⚠ {error}</p>}
{!loading && !error && convertedAmount !== null && (
<>
<p className="converted-value">
{parseFloat(amount).toLocaleString()} {baseCurrency} =
<strong>{parseFloat(convertedAmount).toLocaleString()} {targetCurrency}</strong>
</p>
{lastUpdated && <p>Taxas atualizadas: {lastUpdated.toLocaleTimeString()}</p>}
</>
)}
</div>
</div>
);
}Passo 5: Variáveis de Ambiente
Crie um arquivo .env na raiz do projeto:
VITE_FINEXLY_API_KEY=sua_chave_api_aquiAdicione .env ao seu .gitignore para proteger sua chave.
Passo 6: Configure a Aplicação
Atualize src/App.tsx:
import CurrencyConverter from "./components/CurrencyConverter";
import "./App.css";
function App() {
return (
<main>
<CurrencyConverter />
<footer>
<p>Taxas de câmbio fornecidas por <a href="https://finexly.com">Finexly</a></p>
</footer>
</main>
);
}
export default App;Tratamento de Casos Especiais
Um conversor pronto para produção precisa tratar vários casos especiais: validar entradas numéricas, avisar sobre seleção da mesma moeda e tratar falhas de rede através do estado de erro no hook.
Otimização de Performance com Debouncing
// src/hooks/useDebounce.ts
import { useState, useEffect } from "react";
export function useDebounce<T>(value: T, delayMs: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delayMs);
return () => clearTimeout(timer);
}, [value, delayMs]);
return debouncedValue;
}Indo Mais Longe
Depois de ter o conversor básico funcionando, aqui estão melhorias de alto valor: gráfico de taxas históricas usando a API histórica do Finexly, conversão multicurrency simultânea, moedas favoritas persistidas no localStorage, e renderização server-side com Next.js.
Perguntas Frequentes
Qual é a melhor API de taxas de câmbio para React? Para aplicações React, você precisa de uma API REST que suporte CORS. A API Finexly suporta CORS em todos os planos e fornece cobertura para mais de 170 moedas com um nível gratuito.
Com que frequência as taxas de câmbio são atualizadas? A API Finexly atualiza as taxas a cada 60 segundos para planos em tempo real e uma vez por dia para planos padrão.
Posso usar uma API de câmbio em uma aplicação React client-side? Sim, se a API suportar CORS. Para aplicações em produção, é melhor rotear as requisições pelo seu próprio backend.
Como evito expor minha chave de API no React?
Para desenvolvimento, use o arquivo .env do Vite com prefixo VITE_. Para produção, roteie as requisições por um Next.js API Route ou função serverless.
Como converto entre duas moedas que não são USD? A API Finexly suporta qualquer moeda base em todos os planos, simplificando bastante esse processo.
Pronto para adicionar taxas de câmbio em tempo real à sua aplicação React? Obtenha sua chave de API gratuita do Finexly — sem necessidade de cartão de crédito. Comece com 1.000 requisições gratuitas por mês e escale conforme crescer.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →