Created
May 15, 2024 08:32
-
-
Save andreialecu/4728d047233005be1ef9262c01ac94ef to your computer and use it in GitHub Desktop.
use-number-formatter hook for react native
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useCallback} from 'react'; | |
import {millify} from '../millify'; | |
import {useAppLanguage} from './use-app-language'; | |
const cachedFormatters = new Map<string, Intl.NumberFormat>(); | |
const cachedResults = new Map<Intl.NumberFormat, Map<number, string>>(); | |
/** | |
* We cache Intl.NumberFormat instances because creating them is slow. | |
* On Android this speeds up this code path about 10x. | |
*/ | |
function getCachedFormatter({ | |
language, | |
opts, | |
}: // notation, | |
{ | |
language: string; | |
opts?: Intl.NumberFormatOptions; | |
}) { | |
const key = [language, JSON.stringify(opts)].join(':'); | |
if (!cachedFormatters.has(key)) { | |
try { | |
cachedFormatters.set( | |
key, | |
Intl.NumberFormat(language, { | |
...opts, | |
// don't use more than 20 digits, otherwise this errors | |
// needed because mocks can generate a number that is out of range | |
minimumFractionDigits: Math.min(opts?.minimumFractionDigits || 0, 20), | |
// cannot use compact because of: https://github.com/facebook/hermes/issues/1190 | |
// notation, | |
}), | |
); | |
} catch (error) { | |
console.error('Error creating number formatter', {language, opts}); | |
cachedFormatters.set( | |
key, | |
Intl.NumberFormat('en-GB', { | |
// don't use more than 20 digits, otherwise this errors | |
// needed because mocks can generate a number that is out of range | |
minimumFractionDigits: Math.min(opts?.minimumFractionDigits || 0, 20), | |
// cannot use compact because of: https://github.com/facebook/hermes/issues/1190 | |
// notation, | |
}), | |
); | |
} | |
} | |
const formatter = cachedFormatters.get(key)!; | |
return { | |
format: (n: number) => { | |
if (!cachedResults.has(formatter)) { | |
cachedResults.set(formatter, new Map()); | |
} | |
const results = cachedResults.get(formatter)!; | |
if (!results.has(n)) { | |
results.set(n, formatter.format(n)); | |
} | |
return results.get(n)!; | |
}, | |
}; | |
} | |
export function useNumberFormatter() { | |
const appLanguage = useAppLanguage(); | |
return useCallback( | |
( | |
n: number | null | undefined, | |
{notation, ...opts}: Intl.NumberFormatOptions = {}, | |
) => { | |
let num = n || 0; | |
const formatter = getCachedFormatter({ | |
language: appLanguage, | |
opts, | |
}); | |
// default to compact | |
if ( | |
(!notation || notation === 'compact') && | |
// only if we can round to 1 digit | |
num % 100 === 0 | |
) { | |
return millify(num, formatter); | |
} | |
return formatter.format(num); | |
}, | |
[appLanguage], | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment