Skip to content

Commit e4daca0

Browse files
authored
Merge pull request #30 from udecode/fix/stable-useNameStore
Memorize return value of `use<Name>Store`
2 parents 7d7fd75 + 38b929d commit e4daca0

File tree

3 files changed

+137
-112
lines changed

3 files changed

+137
-112
lines changed

.changeset/blue-moose-hug.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'jotai-x': patch
3+
---
4+
5+
Fix: Return value of `use<Name>Store` is not memorized

packages/jotai-x/src/createAtomStore.spec.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,14 @@ describe('createAtomStore', () => {
11021102
expect(getByText('Change Callback new')).toBeInTheDocument();
11031103
expect(getByText('useBecameFriends: true')).toBeInTheDocument();
11041104
});
1105+
1106+
it('returns a stable store from useNameStore', () => {
1107+
const { result, rerender } = renderHook(useMyTestStoreStore);
1108+
const first = result.current;
1109+
rerender();
1110+
const second = result.current;
1111+
expect(first === second).toBeTruthy();
1112+
});
11051113
});
11061114

11071115
describe('scoped providers', () => {

packages/jotai-x/src/createAtomStore.ts

+124-112
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22
import { getDefaultStore, useAtom, useAtomValue, useSetAtom } from 'jotai';
33
import { selectAtom, useHydrateAtoms } from 'jotai/utils';
44

@@ -473,6 +473,15 @@ const convertScopeShorthand = (
473473
? { scope: optionsOrScope }
474474
: optionsOrScope;
475475

476+
const useConvertScopeShorthand: typeof convertScopeShorthand = (
477+
optionsOrScope
478+
) => {
479+
const convertedOptions = convertScopeShorthand(optionsOrScope);
480+
// Works because all values are primitives
481+
// eslint-disable-next-line react-compiler/react-compiler
482+
return useMemo(() => convertedOptions, Object.values(convertedOptions));
483+
};
484+
476485
const identity = (x: any) => x;
477486

478487
export interface CreateAtomStoreOptions<
@@ -755,118 +764,121 @@ Please wrap them with useCallback or configure the deps array correctly.`
755764
};
756765

757766
const useStoreApi: UseStoreApi<T, E> = (options = {}) => {
758-
const scopedOptions = convertScopeShorthand(options);
759-
const store = useStore(scopedOptions);
760-
761-
return {
762-
// store.use<Key>Value()
763-
...(withStoreAndOptions(
764-
atomsOfUseValue,
765-
getUseValueIndex,
766-
store,
767-
scopedOptions
768-
) as UseKeyValueApis<MyStoreAtoms>),
769-
// store.get<Key>()
770-
...(withStoreAndOptions(
771-
atomsOfGet,
772-
getGetIndex,
773-
store,
774-
scopedOptions
775-
) as GetKeyApis<MyStoreAtoms>),
776-
// store.useSet<Key>()
777-
...(withStoreAndOptions(
778-
atomsOfUseSet,
779-
getUseSetIndex,
780-
store,
781-
scopedOptions
782-
) as UseSetKeyApis<MyStoreAtoms>),
783-
// store.set<Key>(...args)
784-
...(withStoreAndOptions(
785-
atomsOfSet,
786-
getSetIndex,
787-
store,
788-
scopedOptions
789-
) as SetKeyApis<MyStoreAtoms>),
790-
// store.use<Key>State()
791-
...(withStoreAndOptions(
792-
atomsOfUseState,
793-
getUseStateIndex,
794-
store,
795-
scopedOptions
796-
) as UseKeyStateApis<MyStoreAtoms>),
797-
// store.subscribe<Key>(callback)
798-
...(withStoreAndOptions(
799-
atomsOfSubscribe,
800-
getSubscribeIndex,
801-
store,
802-
scopedOptions
803-
) as SubscribeKeyApis<MyStoreAtoms>),
804-
// store.useValue('key')
805-
useValue: withKeyAndStoreAndOptions(
806-
atomsOfUseValue,
807-
store,
808-
scopedOptions
809-
) as UseParamKeyValueApi<MyStoreAtoms>,
810-
// store.get('key')
811-
get: withKeyAndStoreAndOptions(
812-
atomsOfGet,
813-
store,
814-
scopedOptions
815-
) as GetParamKeyApi<MyStoreAtoms>,
816-
// store.useSet('key')
817-
useSet: withKeyAndStoreAndOptions(
818-
atomsOfUseSet,
819-
store,
820-
scopedOptions
821-
) as UseSetParamKeyApi<MyStoreAtoms>,
822-
// store.set('key', ...args)
823-
set: withKeyAndStoreAndOptions(
824-
atomsOfSet,
825-
store,
826-
scopedOptions
827-
) as SetParamKeyApi<MyStoreAtoms>,
828-
// store.useState('key')
829-
useState: withKeyAndStoreAndOptions(
830-
atomsOfUseState,
831-
store,
832-
scopedOptions
833-
) as UseParamKeyStateApi<MyStoreAtoms>,
834-
// store.subscribe('key', callback)
835-
subscribe: withKeyAndStoreAndOptions(
836-
atomsOfSubscribe,
837-
store,
838-
scopedOptions
839-
) as SubscribeParamKeyApi<MyStoreAtoms>,
840-
// store.useAtomValue(atomConfig)
841-
useAtomValue: ((atomConfig, selector, equalityFnOrDeps, deps) =>
842-
// eslint-disable-next-line react-compiler/react-compiler
843-
useAtomValueWithStore(
844-
atomConfig,
767+
const convertedOptions = useConvertScopeShorthand(options);
768+
const store = useStore(convertedOptions);
769+
770+
return useMemo(
771+
() => ({
772+
// store.use<Key>Value()
773+
...(withStoreAndOptions(
774+
atomsOfUseValue,
775+
getUseValueIndex,
845776
store,
846-
scopedOptions,
847-
selector,
848-
equalityFnOrDeps,
849-
deps
850-
)) as UseAtomParamValueApi,
851-
// store.getAtom(atomConfig)
852-
getAtom: (atomConfig) =>
853-
getAtomWithStore(atomConfig, store, scopedOptions),
854-
// store.useSetAtom(atomConfig)
855-
useSetAtom: (atomConfig) =>
856-
// eslint-disable-next-line react-compiler/react-compiler
857-
useSetAtomWithStore(atomConfig, store, scopedOptions),
858-
// store.setAtom(atomConfig, ...args)
859-
setAtom: (atomConfig) =>
860-
setAtomWithStore(atomConfig, store, scopedOptions),
861-
// store.useAtomState(atomConfig)
862-
useAtomState: (atomConfig) =>
863-
// eslint-disable-next-line react-compiler/react-compiler
864-
useAtomStateWithStore(atomConfig, store, scopedOptions),
865-
// store.subscribeAtom(atomConfig, callback)
866-
subscribeAtom: (atomConfig) =>
867-
subscribeAtomWithStore(atomConfig, store, scopedOptions),
868-
store,
869-
};
777+
convertedOptions
778+
) as UseKeyValueApis<MyStoreAtoms>),
779+
// store.get<Key>()
780+
...(withStoreAndOptions(
781+
atomsOfGet,
782+
getGetIndex,
783+
store,
784+
convertedOptions
785+
) as GetKeyApis<MyStoreAtoms>),
786+
// store.useSet<Key>()
787+
...(withStoreAndOptions(
788+
atomsOfUseSet,
789+
getUseSetIndex,
790+
store,
791+
convertedOptions
792+
) as UseSetKeyApis<MyStoreAtoms>),
793+
// store.set<Key>(...args)
794+
...(withStoreAndOptions(
795+
atomsOfSet,
796+
getSetIndex,
797+
store,
798+
convertedOptions
799+
) as SetKeyApis<MyStoreAtoms>),
800+
// store.use<Key>State()
801+
...(withStoreAndOptions(
802+
atomsOfUseState,
803+
getUseStateIndex,
804+
store,
805+
convertedOptions
806+
) as UseKeyStateApis<MyStoreAtoms>),
807+
// store.subscribe<Key>(callback)
808+
...(withStoreAndOptions(
809+
atomsOfSubscribe,
810+
getSubscribeIndex,
811+
store,
812+
convertedOptions
813+
) as SubscribeKeyApis<MyStoreAtoms>),
814+
// store.useValue('key')
815+
useValue: withKeyAndStoreAndOptions(
816+
atomsOfUseValue,
817+
store,
818+
convertedOptions
819+
) as UseParamKeyValueApi<MyStoreAtoms>,
820+
// store.get('key')
821+
get: withKeyAndStoreAndOptions(
822+
atomsOfGet,
823+
store,
824+
convertedOptions
825+
) as GetParamKeyApi<MyStoreAtoms>,
826+
// store.useSet('key')
827+
useSet: withKeyAndStoreAndOptions(
828+
atomsOfUseSet,
829+
store,
830+
convertedOptions
831+
) as UseSetParamKeyApi<MyStoreAtoms>,
832+
// store.set('key', ...args)
833+
set: withKeyAndStoreAndOptions(
834+
atomsOfSet,
835+
store,
836+
convertedOptions
837+
) as SetParamKeyApi<MyStoreAtoms>,
838+
// store.useState('key')
839+
useState: withKeyAndStoreAndOptions(
840+
atomsOfUseState,
841+
store,
842+
convertedOptions
843+
) as UseParamKeyStateApi<MyStoreAtoms>,
844+
// store.subscribe('key', callback)
845+
subscribe: withKeyAndStoreAndOptions(
846+
atomsOfSubscribe,
847+
store,
848+
convertedOptions
849+
) as SubscribeParamKeyApi<MyStoreAtoms>,
850+
// store.useAtomValue(atomConfig)
851+
useAtomValue: ((atomConfig, selector, equalityFnOrDeps, deps) =>
852+
// eslint-disable-next-line react-compiler/react-compiler
853+
useAtomValueWithStore(
854+
atomConfig,
855+
store,
856+
convertedOptions,
857+
selector,
858+
equalityFnOrDeps,
859+
deps
860+
)) as UseAtomParamValueApi,
861+
// store.getAtom(atomConfig)
862+
getAtom: (atomConfig) =>
863+
getAtomWithStore(atomConfig, store, convertedOptions),
864+
// store.useSetAtom(atomConfig)
865+
useSetAtom: (atomConfig) =>
866+
// eslint-disable-next-line react-compiler/react-compiler
867+
useSetAtomWithStore(atomConfig, store, convertedOptions),
868+
// store.setAtom(atomConfig, ...args)
869+
setAtom: (atomConfig) =>
870+
setAtomWithStore(atomConfig, store, convertedOptions),
871+
// store.useAtomState(atomConfig)
872+
useAtomState: (atomConfig) =>
873+
// eslint-disable-next-line react-compiler/react-compiler
874+
useAtomStateWithStore(atomConfig, store, convertedOptions),
875+
// store.subscribeAtom(atomConfig, callback)
876+
subscribeAtom: (atomConfig) =>
877+
subscribeAtomWithStore(atomConfig, store, convertedOptions),
878+
store,
879+
}),
880+
[store, convertedOptions]
881+
);
870882
};
871883

872884
const useNameState = <K extends keyof StoreAtoms<T, E>>(

0 commit comments

Comments
 (0)