Skip to content

Commit

Permalink
Refactor atomWithFn into util file
Browse files Browse the repository at this point in the history
  • Loading branch information
12joan committed Dec 22, 2023
1 parent 19b63d9 commit 1ace216
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 25 deletions.
32 changes: 32 additions & 0 deletions packages/jotai-x/src/atomWithFn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { atom } from 'jotai';

import type { WritableAtom } from 'jotai/vanilla';

type WrapFn<T> = T extends (...args: infer _A) => infer _R ? { __fn: T } : T;

const wrapFn = <T>(fnOrValue: T): WrapFn<T> =>
(typeof fnOrValue === 'function' ? { __fn: fnOrValue } : fnOrValue) as any;

type UnwrapFn<T> = T extends { __fn: infer U } ? U : T;

const unwrapFn = <T>(wrappedFnOrValue: T): UnwrapFn<T> =>
(wrappedFnOrValue &&
typeof wrappedFnOrValue === 'object' &&
'__fn' in wrappedFnOrValue
? wrappedFnOrValue.__fn
: wrappedFnOrValue) as any;

/**
* Jotai atoms don't allow functions as values by default. This function is a
* drop-in replacement for `atom` that wraps functions in an object while
* leaving non-functions unchanged. The wrapper object should be completely
* invisible to consumers of the atom.
*/
export const atomWithFn = <T>(initialValue: T): WritableAtom<T, [T], void> => {
const baseAtom = atom(wrapFn(initialValue));

return atom(
(get) => unwrapFn(get(baseAtom)) as T,
(_get, set, value) => set(baseAtom, wrapFn(value))
);
};
27 changes: 2 additions & 25 deletions packages/jotai-x/src/createAtomStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { useHydrateAtoms } from 'jotai/utils';

import { atomWithFn } from './atomWithFn';
import { createAtomProvider, useAtomStore } from './createAtomProvider';

import type { ProviderProps } from './createAtomProvider';
Expand All @@ -17,30 +18,6 @@ export type UseAtomOptions = {

type UseAtomOptionsOrScope = UseAtomOptions | string;

// Jotai does not support functions in atoms, so wrap functions in objects
type WrapFn<T> = T extends (...args: infer _A) => infer _R ? { __fn: T } : T;

const wrapFn = <T>(fnOrValue: T): WrapFn<T> =>
(typeof fnOrValue === 'function' ? { __fn: fnOrValue } : fnOrValue) as any;

type UnwrapFn<T> = T extends { __fn: infer U } ? U : T;

const unwrapFn = <T>(wrappedFnOrValue: T): UnwrapFn<T> =>
(wrappedFnOrValue &&
typeof wrappedFnOrValue === 'object' &&
'__fn' in wrappedFnOrValue
? wrappedFnOrValue.__fn
: wrappedFnOrValue) as any;

const atomWithFn = <T>(initialValue: T): SimpleWritableAtom<T> => {
const baseAtom = atom(wrapFn(initialValue));

return atom(
(get) => unwrapFn(get(baseAtom)) as T,
(_get, set, value) => set(baseAtom, wrapFn(value))
);
};

type GetRecord<O> = {
[K in keyof O]: O[K] extends Atom<infer V>
? (options?: UseAtomOptionsOrScope) => V
Expand Down
1 change: 1 addition & 0 deletions packages/jotai-x/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

export * from './atomProvider';
export * from './atomWithFn';
export * from './createAtomProvider';
export * from './createAtomStore';
export * from './useHydrateStore';

0 comments on commit 1ace216

Please sign in to comment.