Skip to content

Commit b5ff55d

Browse files
authored
Merge pull request #25 from udecode/bug
Add tests and bug fixes
2 parents baa1ebc + 6643d02 commit b5ff55d

File tree

5 files changed

+90
-23
lines changed

5 files changed

+90
-23
lines changed

.changeset/bright-coats-peel.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'jotai-x': patch
3+
---
4+
5+
Add test cases and fix bugs

README.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,7 @@ const [stars, setStars] = useAppState('stars');
153153
// With selector and deps
154154
const upperName = useAppValue('name', {
155155
selector: (name) => name.toUpperCase(),
156-
deps: []
157-
});
156+
}, []);
158157
```
159158

160159
#### 2. Store Instance Methods
@@ -204,14 +203,14 @@ const name = useAppValue('name');
204203
// With selector
205204
const upperName = useAppValue('name', {
206205
selector: (name) => name.toUpperCase(),
207-
deps: [] // Optional deps array
208-
});
206+
}, [] // if selector is not memoized, provide deps array
207+
);
209208

210209
// With equality function
211210
const name = useAppValue('name', {
212211
selector: (name) => name,
213212
equalityFn: (prev, next) => prev.length === next.length
214-
});
213+
}, []);
215214
```
216215

217216
#### `use<Name>Set(key)`
@@ -404,7 +403,7 @@ const selector = useCallback((name) => name.toUpperCase(), []);
404403
useUserValue('name', { selector });
405404

406405
// ✅ Correct - provide deps array
407-
useUserValue('name', { selector: (name) => name.toUpperCase(), deps: [] });
406+
useUserValue('name', { selector: (name) => name.toUpperCase() }, []);
408407

409408
// ✅ Correct - no selector
410409
useUserValue('name');

packages/jotai-x/README.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,7 @@ const [stars, setStars] = useAppState('stars');
153153
// With selector and deps
154154
const upperName = useAppValue('name', {
155155
selector: (name) => name.toUpperCase(),
156-
deps: []
157-
});
156+
}, []);
158157
```
159158

160159
#### 2. Store Instance Methods
@@ -204,14 +203,14 @@ const name = useAppValue('name');
204203
// With selector
205204
const upperName = useAppValue('name', {
206205
selector: (name) => name.toUpperCase(),
207-
deps: [] // Optional deps array
208-
});
206+
}, [] // if selector is not memoized, provide deps array
207+
);
209208

210209
// With equality function
211210
const name = useAppValue('name', {
212211
selector: (name) => name,
213212
equalityFn: (prev, next) => prev.length === next.length
214-
});
213+
}, []);
215214
```
216215

217216
#### `use<Name>Set(key)`
@@ -404,7 +403,7 @@ const selector = useCallback((name) => name.toUpperCase(), []);
404403
useUserValue('name', { selector });
405404

406405
// ✅ Correct - provide deps array
407-
useUserValue('name', { selector: (name) => name.toUpperCase(), deps: [] });
406+
useUserValue('name', { selector: (name) => name.toUpperCase() }, []);
408407

409408
// ✅ Correct - no selector
410409
useUserValue('name');

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

+68-4
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ describe('createAtomStore', () => {
2929
arr: INITIAL_ARR,
3030
};
3131

32-
const { useMyTestStoreStore, MyTestStoreProvider } = createAtomStore(
33-
initialTestStoreValue,
34-
{ name: 'myTestStore' as const }
35-
);
32+
const {
33+
myTestStoreStore,
34+
useMyTestStoreStore,
35+
MyTestStoreProvider,
36+
useMyTestStoreValue,
37+
} = createAtomStore(initialTestStoreValue, {
38+
name: 'myTestStore' as const,
39+
});
3640

3741
let numRenderCount = 0;
3842
const NumRenderer = () => {
@@ -92,6 +96,24 @@ describe('createAtomStore', () => {
9296
);
9397
};
9498

99+
let arrNumRenderCountWithOneHook = 0;
100+
const ArrNumRendererWithOneHook = () => {
101+
arrNumRenderCountWithOneHook += 1;
102+
const num = useMyTestStoreValue('num');
103+
const arrNum = useMyTestStoreValue(
104+
'arr',
105+
{
106+
selector: (v) => v[num],
107+
},
108+
[num]
109+
);
110+
return (
111+
<div>
112+
<div>arrNumWithOneHook: {arrNum}</div>
113+
</div>
114+
);
115+
};
116+
95117
let arrNumRenderWithDepsCount = 0;
96118
const ArrNumRendererWithDeps = () => {
97119
arrNumRenderWithDepsCount += 1;
@@ -105,11 +127,31 @@ describe('createAtomStore', () => {
105127
);
106128
};
107129

130+
let arrNumRenderWithDepsAndAtomCount = 0;
131+
const ArrNumRendererWithDepsAndAtom = () => {
132+
arrNumRenderWithDepsAndAtomCount += 1;
133+
const store = useMyTestStoreStore();
134+
const numAtom = myTestStoreStore.atom.num;
135+
const num = store.useAtomValue(numAtom);
136+
const arrAtom = myTestStoreStore.atom.arr;
137+
const arrNum = store.useAtomValue(arrAtom, (v) => v[num], [num]);
138+
return (
139+
<div>
140+
<div>arrNumWithDepsAndAtom: {arrNum}</div>
141+
</div>
142+
);
143+
};
144+
108145
const BadSelectorRenderer = () => {
109146
const arr0 = useMyTestStoreStore().useArrValue((v) => v[0]);
110147
return <div>{arr0}</div>;
111148
};
112149

150+
const BadSelectorRenderer2 = () => {
151+
const arr0 = useMyTestStoreValue('arr', { selector: (v) => v[0] });
152+
return <div>{arr0}</div>;
153+
};
154+
113155
const Buttons = () => {
114156
const store = useMyTestStoreStore();
115157
return (
@@ -154,7 +196,9 @@ describe('createAtomStore', () => {
154196
<Arr0Renderer />
155197
<Arr1Renderer />
156198
<ArrNumRenderer />
199+
<ArrNumRendererWithOneHook />
157200
<ArrNumRendererWithDeps />
201+
<ArrNumRendererWithDepsAndAtom />
158202
<Buttons />
159203
</MyTestStoreProvider>
160204
);
@@ -166,7 +210,9 @@ describe('createAtomStore', () => {
166210
expect(arr0RenderCount).toBe(2);
167211
expect(arr1RenderCount).toBe(2);
168212
expect(arrNumRenderCount).toBe(2);
213+
expect(arrNumRenderCountWithOneHook).toBe(2);
169214
expect(arrNumRenderWithDepsCount).toBe(2);
215+
expect(arrNumRenderWithDepsAndAtomCount).toBe(2);
170216
expect(getByText('arrNum: alice')).toBeInTheDocument();
171217
expect(getByText('arrNumWithDeps: alice')).toBeInTheDocument();
172218

@@ -177,7 +223,9 @@ describe('createAtomStore', () => {
177223
expect(arr0RenderCount).toBe(2);
178224
expect(arr1RenderCount).toBe(2);
179225
expect(arrNumRenderCount).toBe(5);
226+
expect(arrNumRenderCountWithOneHook).toBe(5);
180227
expect(arrNumRenderWithDepsCount).toBe(5);
228+
expect(arrNumRenderWithDepsAndAtomCount).toBe(5);
181229
expect(getByText('arrNum: bob')).toBeInTheDocument();
182230
expect(getByText('arrNumWithDeps: bob')).toBeInTheDocument();
183231

@@ -188,7 +236,9 @@ describe('createAtomStore', () => {
188236
expect(arr0RenderCount).toBe(2);
189237
expect(arr1RenderCount).toBe(2);
190238
expect(arrNumRenderCount).toBe(5);
239+
expect(arrNumRenderCountWithOneHook).toBe(5);
191240
expect(arrNumRenderWithDepsCount).toBe(5);
241+
expect(arrNumRenderWithDepsAndAtomCount).toBe(5);
192242
expect(getByText('arrNum: bob')).toBeInTheDocument();
193243
expect(getByText('arrNumWithDeps: bob')).toBeInTheDocument();
194244

@@ -199,7 +249,9 @@ describe('createAtomStore', () => {
199249
expect(arr0RenderCount).toBe(2);
200250
expect(arr1RenderCount).toBe(2);
201251
expect(arrNumRenderCount).toBe(5);
252+
expect(arrNumRenderCountWithOneHook).toBe(5);
202253
expect(arrNumRenderWithDepsCount).toBe(5);
254+
expect(arrNumRenderWithDepsAndAtomCount).toBe(5);
203255
expect(getByText('arrNum: bob')).toBeInTheDocument();
204256
expect(getByText('arrNumWithDeps: bob')).toBeInTheDocument();
205257

@@ -210,7 +262,9 @@ describe('createAtomStore', () => {
210262
expect(arr0RenderCount).toBe(3);
211263
expect(arr1RenderCount).toBe(2);
212264
expect(arrNumRenderCount).toBe(8);
265+
expect(arrNumRenderCountWithOneHook).toBe(8);
213266
expect(arrNumRenderWithDepsCount).toBe(8);
267+
expect(arrNumRenderWithDepsAndAtomCount).toBe(8);
214268
expect(getByText('arrNum: ava')).toBeInTheDocument();
215269
expect(getByText('arrNumWithDeps: ava')).toBeInTheDocument();
216270
});
@@ -224,6 +278,16 @@ describe('createAtomStore', () => {
224278
)
225279
).toThrow();
226280
});
281+
282+
it('Throw error is user does memoize selector 2', () => {
283+
expect(() =>
284+
render(
285+
<MyTestStoreProvider>
286+
<BadSelectorRenderer2 />
287+
</MyTestStoreProvider>
288+
)
289+
).toThrow();
290+
});
227291
});
228292

229293
describe('single provider', () => {

packages/jotai-x/src/createAtomStore.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -493,10 +493,10 @@ export interface CreateAtomStoreOptions<
493493
* Each property will have a getter and setter.
494494
*
495495
* @example
496-
* const { exampleStore, useExampleStore } = createAtomStore({ count: 1, say: 'hello' }, { name: 'example' as const })
497-
* const [count, setCount] = useExampleStore().useCountState()
498-
* const say = useExampleStore().useSayValue()
499-
* const setSay = useExampleStore().useSetSay()
496+
* const { exampleStore, useExampleStore, useExampleValue, useExampleState, useExampleSet } = createAtomStore({ count: 1, say: 'hello' }, { name: 'example' as const })
497+
* const [count, setCount] = useExampleState()
498+
* const say = useExampleValue('say')
499+
* const setSay = useExampleSet('say')
500500
* setSay('world')
501501
* const countAtom = exampleStore.atom.count
502502
*/
@@ -591,7 +591,7 @@ export const createAtomStore = <
591591
if (renderCount > infiniteRenderDetectionLimit) {
592592
throw new Error(
593593
`
594-
use<Key>Value/useValue has rendered ${infiniteRenderDetectionLimit} times in the same render.
594+
use<Key>Value/useValue/use<StoreName>Value has rendered ${infiniteRenderDetectionLimit} times in the same render.
595595
It is very likely to have fallen into an infinite loop.
596596
That is because you do not memoize the selector/equalityFn function param.
597597
Please wrap them with useCallback or configure the deps array correctly.`
@@ -898,8 +898,8 @@ Please wrap them with useCallback or configure the deps array correctly.`
898898
store,
899899
options,
900900
selector as any,
901-
equalityFn as any,
902-
deps
901+
equalityFn ?? deps,
902+
equalityFn && deps
903903
);
904904
};
905905

0 commit comments

Comments
 (0)