Voltar ao Blog

Como Construir um Conversor de Moedas com React e uma API de Taxas de Câmbio em Tempo Real

V
Vlado Grigirov
April 07, 2026
"Currency API" "React" "Tutorial" "JavaScript" "Exchange Rates" "Finexly"

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

Pré-requisitos

Antes de começar, certifique-se de ter:

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 dev

Seu 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=USD

Uma 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_API

Você 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} =&nbsp;
              <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_aqui

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

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 →

Compartilhar este artigo