Currency Switcher
A fluid fintech currency switcher with live exchange rates, bidirectional inputs, and spring-driven interactions.
Bidirectional amount input
Edit either the send or receive field; the opposite amount updates live from the current exchange rate.
Spring-physics card swap
From/to currencies animate past each other with a double-tap guard so rapid taps never corrupt state.
Live exchange rates
Fetches from the Frankfurter API out of the box, with a 15-minute in-memory cache and stale-request cancellation. Swap in any backend via an injectable fetcher prop.
Morph-transition picker
The currency dropdown spring-scales from its pill anchor on open and collapses back on dismiss. Searchable by code or country name, with a double-select guard.
Installation
Copy the command directly into your project.
Install the needed dependencies
Use in your app
Built to match the preview exactly — for the complete implementation, check the repo on GitHub.
import { StyleSheet, View } from "react-native";
import { CurrencySwitcher } from "../../components/currency-switch/CurrencySwitcher";
export default function HomeScreen() {
return (
<View style={styles.container}>
<CurrencySwitcher />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#000",
},
});API
<CurrencySwitcher>
| Prop | Type | Default | Description |
|---|---|---|---|
| currencies | readonly Currency[] | built-in list | The currency list shown in both pickers. |
| defaultFromCurrency | Currency | USD | Currency shown in the send card on first render. |
| defaultToCurrency | Currency | INR | Currency shown in the receive card on first render. |
| defaultSendAmount | string | "300" | Amount pre-filled in the send field on first render. |
| rate | number | — | Pass an explicit rate to skip the internal Frankfurter fetch entirely. |
| isRateLoading | boolean | — | When using an external rate, set this while your rate loads to show the Fetching… state. |
| fee | number | 76.87 | Transaction fee displayed in the info section. |
| arrivalEstimate | string | "By Friday" | Arrival estimate string displayed in the info section. |
| swapIcon | ReactNode | built-in SwapIcon | Replace the swap button icon with any node. |
| style | StyleProp<ViewStyle> | — | Additional styles applied to the root container. |
| onAmountChange | (amount: string) => void | — | Fires on every keystroke with the send-side amount as a string. |
| onCurrencyChange | (from: Currency, to: Currency) => void | — | Fires when either currency is changed or the cards are swapped. |
| onSend | (details: { amount: number; from: Currency; to: Currency }) => void | — | Fires on Send tap with the current send amount and both currencies. |
<CurrencyPicker>
| Prop | Type | Default | Description |
|---|---|---|---|
| visible | boolean | — | Controls open/close. Morph-animates in and out automatically. |
| anchorPosition | AnchorPosition | null | — | Screen position of the pill that triggered the picker, used to anchor the dropdown. |
| selectedCode | string | — | The currently selected currency code. Highlighted in the list. |
| currencies | readonly Currency[] | — | The full list to render and filter. |
| onSelect | (currency: Currency) => void | — | Fired once per selection; double-select is guarded internally. |
| onClose | () => void | — | Fired on backdrop tap or after a selection. |
useExchangeRate
const { rate, isLoading, error, refresh } = useExchangeRate(base, target, options);| Option | Type | Default | Description |
|---|---|---|---|
| fetcher | ExchangeRateFetcher | Frankfurter | Any (base, target) => Promise<number> function. |
| cacheTtlMs | number | 900000 (15 min) | How long a cached rate is considered fresh before refetch. |
| enabled | boolean | true | Set to false to skip fetching entirely — used internally when the rate prop is provided. |
Types
interface Currency {
readonly code: string;
readonly symbol: string;
readonly name: string;
readonly countryCode: string; // ISO 3166-1 alpha-2, used for flag CDN lookup
}
type ExchangeRateFetcher = (base: string, target: string) => Promise<number>;
interface AnchorPosition {
readonly x: number;
readonly y: number;
readonly width: number;
readonly height: number;
}