react-native-cached-flags
v1.2.0
Published
React Native country flag component with emoji fallback and persistent SVG caching
Downloads
363
Maintainers
Readme
🇨🇲 react-native-cached-flags
Zero runtime dependencies. Persistent SVG caching. Request deduplication.
Country flags for React Native — emoji or SVG, always fast.

Why this package?
Most flag packages either render emojis (fast but low quality) or fetch SVGs (great quality but wasteful). This package does both — and caches aggressively:
- Emoji mode — zero network requests, instant render
- SVG mode — fetches once, stores to device storage permanently, never hits the network again for that flag
- Deduplication — rendering 50 of the same flag simultaneously fires exactly 1 network request
- Zero runtime dependencies — emoji generation is built-in, no extra packages pulled into your app
Installation
# npm
npm install react-native-cached-flags
# yarn
yarn add react-native-cached-flags
# bun
bun add react-native-cached-flagsPeer dependencies
npm install react-native-svg @react-native-async-storage/async-storageFor Expo projects use
npx expo installto get compatible versions.
Usage
Emoji mode (default)
Zero network requests. Renders the platform emoji for the country. Accepts ISO 3166-1 alpha-2 codes, IETF language tags, and ISO subdivision codes.
import { CountryFlag } from 'react-native-cached-flags';
// ISO 3166-1 alpha-2
<CountryFlag isoCode="CM" size={32} />
// IETF language tag
<CountryFlag isoCode="en-US" size={32} />
// ISO subdivision (renders parent country flag)
<CountryFlag isoCode="GB-SCT" size={32} />SVG mode (cached)
Fetches once, caches permanently to device storage. Instant on every subsequent render — even after app restarts.
<CountryFlag isoCode="CM" size={32} useSvg />Custom aspect ratio
<CountryFlag isoCode="CM" size={32} useSvg aspectRatio="1:1" />Offline fallback
Show an emoji instead of a placeholder when offline and the flag is not yet cached:
<CountryFlag isoCode="CM" size={32} useSvg useFallbackEmoji />Cache TTL
Flags rarely change, but they do occasionally. Set an expiry to ensure stale flags are eventually refreshed:
<CountryFlag isoCode="CM" size={32} useSvg cacheTTLDays={90} />Disable caching
Always fetch a fresh flag while still deduplicating simultaneous requests:
<CountryFlag isoCode="CM" size={32} useSvg disableCache />Load and error callbacks
<CountryFlag
isoCode="CM"
size={32}
useSvg
onLoad={() => console.log('Flag ready')}
onError={(message) => console.error('Flag failed:', message)}
/>Preload flags before rendering
Warm the cache ahead of time — ideal for onboarding flows and country pickers:
import { preloadFlags } from 'react-native-cached-flags';
await preloadFlags(['US', 'CM', 'FR', 'DE', 'JP'], {
aspectRatio: '4:3',
ttlDays: 30,
});
// All flags are now cached — rendering will be instantProps
| Prop | Type | Default | Description |
| ------------------ | --------------------------- | ----------- | ----------------------------------------------------------------- |
| isoCode | string | — | ISO 3166-1 alpha-2, IETF tag (en-US), or subdivision (GB-SCT) |
| size | number | — | Width in dp — height derived from aspect ratio |
| useSvg | boolean | false | Use SVG with persistent cache instead of emoji |
| aspectRatio | '4:3' \| '1:1' | '4:3' | Aspect ratio of the rendered flag |
| useFallbackEmoji | boolean | false | Show emoji if offline and flag not yet cached |
| cacheTTLDays | number | undefined | Days before a cached flag expires and is re-fetched |
| disableCache | boolean | false | Skip cache — always fetch fresh (deduplication still applies) |
| placeholderColor | string | '#E5E7EB' | Background color shown while SVG is loading |
| borderRadius | number | 0 | Corner radius on the flag container |
| onLoad | () => void | — | Called when SVG renders successfully |
| onError | (message: string) => void | — | Called when flag fails to load, with error description |
| testID | string | — | Test ID for automated testing |
Accepted isoCode formats
| Format | Example | Result |
| ------------------ | ------------------------------- | -------------------------- |
| ISO 3166-1 alpha-2 | "US", "CM", "FR" | Direct match |
| IETF language tag | "en-US", "pt-BR", "zh-CN" | Region subtag extracted |
| ISO subdivision | "GB-SCT", "GB-ENG" | Parent country used (🇬🇧) |
| Bare language tag | "pl", "en" | Falls back to 🏳️ |
Offline behaviour
| Scenario | useFallbackEmoji | Result |
| ------------------- | ------------------ | ----------------------------- |
| Cache hit | any | SVG renders instantly |
| Cache miss, online | any | Fetch once, cache, render SVG |
| Cache miss, offline | false | Dashed placeholder shown |
| Cache miss, offline | true | Emoji fallback rendered |
| HTTP error | any | Default grey SVG shown |
Request deduplication
Rendering the same flag multiple times simultaneously triggers only one network request. All instances share the in-flight promise and render together when it resolves.
// 50 renders → exactly 1 network request
{
Array.from({ length: 50 }).map((_, i) => (
<CountryFlag key={i} isoCode="CM" size={32} useSvg />
));
}Web & cross-platform usage — getFlagUrl
The CountryFlag component renders SVGs using react-native-svg, which is
native-only. For web contexts — React, Next.js, or any non-native environment —
use getFlagUrl to get the CDN URL directly and render it however you need.
┌─────────────────────────────────────────────────────────┐
│ │
│ isoCode / IETF tag / subdivision │
│ │ │
│ ▼ │
│ getFlagUrl('CM', { aspectRatio: '4:3' }) │
│ │ │
│ ▼ │
│ 'https://flagicons.lipis.dev/flags/4x3/cm.svg' │
│ │ │
│ ┌──────┴──────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ <img src={url} /> <Image src={url} /> │
│ (React / web) (Next.js) │
│ │
└─────────────────────────────────────────────────────────┘import { getFlagUrl } from 'react-native-cached-flags';
import { Platform } from 'react-native';
import { SvgUri } from 'react-native-svg';
// Get the URL
const url = getFlagUrl('CM'); // '...flags/4x3/cm.svg'
const squareUrl = getFlagUrl('CM', { aspectRatio: '1:1' }); // '...flags/1x1/cm.svg'
const fromTag = getFlagUrl('en-US'); // '...flags/4x3/us.svg'
const fromSub = getFlagUrl('GB-SCT'); // '...flags/4x3/gb.svg'
const invalid = getFlagUrl('pl'); // null
// React Native — use CountryFlag (cached) or SvgUri (uncached)
if (Platform.OS !== 'web') {
return <CountryFlag isoCode="CM" size={32} useSvg />;
}
// Web — use a standard img tag
if (url) {
return <img src={url} width={40} height={30} alt="Cameroon flag" />;
}Platform-conditional pattern
For codebases targeting both native and web:
import { Platform } from 'react-native';
import { CountryFlag, getFlagUrl } from 'react-native-cached-flags';
function Flag({ isoCode, size }: { isoCode: string; size: number }) {
if (Platform.OS === 'web') {
const url = getFlagUrl(isoCode);
if (!url) return null;
return (
<img
src={url}
width={size}
height={size * 0.75}
alt={`${isoCode} flag`}
style={{ borderRadius: 4, objectFit: 'cover' }}
/>
);
}
return <CountryFlag isoCode={isoCode} size={size} useSvg />;
}Next.js example
import Image from 'next/image';
import { getFlagUrl } from 'react-native-cached-flags';
export function FlagImage({ isoCode }: { isoCode: string }) {
const url = getFlagUrl(isoCode);
if (!url) return null;
return <Image src={url} width={40} height={30} alt={`${isoCode} flag`} />;
}Note:
getFlagUrlreturns a URL only — no caching, no deduplication. On native, preferCountryFlagwithuseSvgfor the full caching benefits. On web, your browser's HTTP cache handles repeated requests automatically.
Cache utilities
import {
preloadFlags,
clearFlagCache,
clearAllFlagVariants,
clearAllFlagCache,
getCachedFlagsCount,
getCacheSizeKB,
getNetworkFetchCount,
resetNetworkFetchCount,
} from 'react-native-cached-flags';
// Preload a set of flags into cache before rendering
await preloadFlags(['US', 'CM', 'FR'], { aspectRatio: '4:3', ttlDays: 30 });
// Remove one flag for a specific aspect ratio
await clearFlagCache('CM', '4:3');
// Remove all cached variants of a flag (all aspect ratios)
await clearAllFlagVariants('CM');
// Clear the entire cache
await clearAllFlagCache();
// Cache stats
const count = await getCachedFlagsCount(); // number of flags currently cached
const size = await getCacheSizeKB(); // total cache size in KB
// Network request tracking (resets on app restart)
const fetches = getNetworkFetchCount();
resetNetworkFetchCount();useCacheStats hook
Reactive hook that automatically reflects cache state. Eliminates manual refresh calls.
import { useCacheStats } from 'react-native-cached-flags';
// Manual refresh
const { count, sizeKB, fetchCount, loading, refresh } = useCacheStats();
// Auto-polling every 2 seconds
const { count, sizeKB, fetchCount } = useCacheStats({ pollIntervalMs: 2000 });
// With clearAndRefresh helper
const { clearAndRefresh } = useCacheStats();
await clearAndRefresh('CM'); // clears all CM variants and refreshes stats| Field | Type | Description |
| ----------------- | ------------------------------------ | ---------------------------------------- |
| count | number | Number of flags currently in cache |
| sizeKB | number | Total cache size in KB |
| fetchCount | number | Network requests made this session |
| loading | boolean | Whether stats are being loaded |
| refresh | () => Promise<void> | Manually trigger a stats refresh |
| clearAndRefresh | (isoCode: string) => Promise<void> | Clear all variants of a flag and refresh |
Emoji generation
Flag emojis are generated natively — no external dependency required.
Uses Unicode regional indicator symbols (U+1F1E6–U+1F1FF).
import {
countryCodeToFlagEmoji,
extractCountryCode,
} from 'react-native-cached-flags';
countryCodeToFlagEmoji('US'); // '🇺🇸'
countryCodeToFlagEmoji('en-US'); // '🇺🇸'
countryCodeToFlagEmoji('GB-SCT'); // '🇬🇧'
countryCodeToFlagEmoji('pl'); // null
extractCountryCode('en-US'); // 'US'
extractCountryCode('GB-SCT'); // 'GB'
extractCountryCode('pl'); // nullHow caching works
First render → cache miss → fetch from CDN → save to AsyncStorage
Simultaneous renders → deduplicated → 1 fetch shared across all instances
All future renders → cache hit → instant, no network
After app restart → cache hit → still instant (persisted to disk)
TTL expired → cache miss → re-fetches fresh copy from CDN
disableCache=true → skip cache → always fresh, still deduplicated
Offline, no cache → placeholder or emoji fallback (failures never cached)
Web (getFlagUrl) → URL only → browser HTTP cache handles repeatsSVG flags are sourced from flagicons.lipis.dev — all flags share a consistent aspect ratio so they align perfectly side by side.
Changelog
[1.2.0]
- Added
getFlagUrlutility for web and cross-platform usage
[1.1.0]
- Removed
country-code-to-flag-emojidependency — zero runtime dependencies - Built-in emoji generation from Unicode regional indicator symbols
- Accepts IETF language tags (
en-US,pt-BR) and ISO subdivision codes (GB-SCT) - Added
disableCacheprop — always fetch fresh while deduplication still applies - Added
clearAllFlagVariantsutility — remove all aspect ratio variants for one flag - Added
useCacheStatshook with optional polling andclearAndRefreshhelper - Fixed cache key double-building bug (cache now works correctly for all aspect ratios)
- Fixed
clearAllFlagVariantscase sensitivity bug
[1.0.0]
- Added request deduplication — N simultaneous renders trigger exactly 1 network request
- Added
cacheTTLDaysprop — optional cache expiry in days - Added
onLoadandonErrorcallback props - Added
preloadFlagsutility for warming the cache ahead of rendering - Cache storage format updated to JSON payload with timestamp for TTL support
- Backward compatible with caches from v0.x
- First stable release
[0.2.0]
- Added
aspectRatioprop ('4:3' | '1:1') - Added
useFallbackEmojiprop for offline graceful degradation - Fixed: network failures no longer cached permanently
- Offline state shows dashed border placeholder
- Cache keys include aspect ratio
[0.1.0]
- Added
getCachedFlagsCount,getCacheSizeKB - Added
getNetworkFetchCount,resetNetworkFetchCount - Improved
clearAllFlagCacheto use batchmultiRemove
[0.0.1]
- Initial release
CountryFlagcomponent with emoji and SVG modes- Persistent SVG caching via AsyncStorage
clearFlagCache,clearAllFlagCache
License
MIT © SiandjaRemy
