-
Hello! I've been trying to figure out how I could wrap Intl.NumberFormatter in an atom/atomFamily. I know functions need to be wrapped in an object like this: My ideal scenario would be something like this:
or this
Weirdly, any version I try ends up in a "maximum callstack exceeded" error:
I feel like I'm very close but I'm getting something wrong. Does anyone know? Thank you! |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 6 replies
-
The object passed to |
Beta Was this translation helpful? Give feedback.
-
function createMemoizedNumberFormatterFamily(): (
options: Intl.NumberFormatOptions
) => Atom<Intl.NumberFormat> {
const numberFormatterFamily = atomFamily(
(options: Intl.NumberFormatOptions): Atom<Intl.NumberFormat> => {
const formatObjAtom = atom({
fn: new Intl.NumberFormat(navigator.language, options),
});
return atom((get) => get(formatObjAtom).fn);
}
);
const keyMap = new Map<string, Intl.NumberFormatOptions>();
return function getNumberFormatAtom(options: Intl.NumberFormatOptions) {
const keyString = stableStringify(options);
if (!keyMap.has(keyString)) {
keyMap.set(keyString, options);
}
const stableOptions = keyMap.get(keyString)!;
return numberFormatterFamily(stableOptions);
};
}
function stableStringify(obj: unknown): string {
if (Array.isArray(obj)) {
return JSON.stringify(obj.map(stableStringify));
}
if (typeof obj === "object" && obj !== null) {
const sortedKeys = Object.keys(obj).sort();
const sortedObj: Record<string, string> = {};
for (const key of sortedKeys) {
sortedObj[key] = stableStringify(obj[key]);
}
return JSON.stringify(sortedObj);
}
return JSON.stringify(obj);
}
const numberFormatterFamily = createMemoizedNumberFormatterFamily(); |
Beta Was this translation helpful? Give feedback.
-
Hi @rothsandro and @dmaskasky I think I was able to get it working with both of your examples put together, thank you! @dmaskasky although I can see what Here's what I did get working and I feel like these are functionally equivalent. Both const areEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
const atomAtom = atomFamily((options) => {
const formatObjAtom = atom({
fn: new Intl.NumberFormat(navigator.language, options),
});
return atom((get) => get(formatObjAtom).fn);
}, areEqual);
const memoAtom = atomFamily((options) => {
const formatObjAtom = useMemo(() => {
return new Intl.NumberFormat(navigator.language, options)
}, [options])
return atom(() => {
return {
format: (value: number) => {
return formatObjAtom.format(value)
}
}
})
}, areEqual); Follow up thoughts:
Thanks again! |
Beta Was this translation helpful? Give feedback.
-
Happy to explain my code. Here's a simple codesandbox to start 😊. I realize I should have taken more time to prepare my answer. There were several mistakes in my original solution. I've corrected them now.
Let me know if you have any further questions. You implementation looks mostly correct. But I'm concerned about the I think along the lines of what you have built, something like this could work: const areEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
const formatFamily = atomFamily((options) => {
const formatObjAtom = atom({
fn: new Intl.NumberFormat(navigator.language, options),
});
return atom((get) => get(formatObjAtom).fn);
}, areEqual);
function Display() {
const formatter = useAtomValue(formatFamily({ maximumFractionDigits: 8 }))
return <p>{formatter.format(100)}</p>
} const formatFamily = atomFamily((options) => {
const formatObjAtom = atom({
fn: new Intl.NumberFormat(navigator.language, options),
});
return atom((get) => get(formatObjAtom).fn);
});
const displayFormatAtom = formatFamily({ maximumFractionDigits: 8 })
function Display() {
const formatter = useAtomValue(displayFormatAtom)
return <p>{formatter.format(100)}</p>
} const formatFamily = atomFamily((options) => {
const formatObjAtom = atom({
fn: new Intl.NumberFormat(navigator.language, options),
});
return atom((get) => get(formatObjAtom).fn);
});
function Display() {
const displayOptions = useMemo(() => ({ maximumFractionDigits: 8 }),[])
const formatter = useAtomValue(formatFamily(displayOptions))
return <p>{formatter.format(100)}</p>
} const formatFamily = atomFamily((options) => {
const formatObjAtom = atom({
fn: new Intl.NumberFormat(navigator.language, options),
});
return atom((get) => get(formatObjAtom).fn);
});
function Display() {
const displayFormatAtom = useMemo(() => formatFamily({ maximumFractionDigits: 8 }),[])
const formatter = useAtomValue(displayFormatAtom)
return <p>{formatter.format(100)}</p>
} |
Beta Was this translation helpful? Give feedback.
Happy to explain my code. Here's a simple codesandbox to start 😊.
I realize I should have taken more time to prepare my answer. There were several mistakes in my original solution. I've corrected them now.
createMemoizedNumberFormatterFamily
doesn't accept any arguments. It sets up initial conditions (map + atomFamily), to be used later. This is the key component of memoizing theIntl.NumberFormatOptions
. …