From 73c5766bded839ee81a2966823303dac33210ce0 Mon Sep 17 00:00:00 2001 From: AMin Date: Sun, 25 Oct 2026 13:48:33 +0800 Subject: [PATCH 1/3] fix(useStateList): wrap negative multiples to the first state --- src/useStateList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useStateList.ts b/src/useStateList.ts index 748e317c8a..8c7b6f346a 100644 --- a/src/useStateList.ts +++ b/src/useStateList.ts @@ -47,7 +47,7 @@ export default function useStateList(stateSet: T[] = []): UseStateListReturn< index.current = newIndex >= 0 ? newIndex % stateSet.length - : stateSet.length + (newIndex % stateSet.length); + : (stateSet.length + (newIndex % stateSet.length)) % stateSet.length; update(); }, setState: (state: T) => { From a3f45b3fd8a0037b97cf2b745709601039338494 Mon Sep 17 00:00:00 2001 From: AMin Date: Sun, 25 Oct 2026 13:49:04 +0800 Subject: [PATCH 2/3] test(useStateList): cover negative multiples and single-item lists --- tests/useStateList.test.ts | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/useStateList.test.ts b/tests/useStateList.test.ts index 21e5641036..2a8cbeafbd 100644 --- a/tests/useStateList.test.ts +++ b/tests/useStateList.test.ts @@ -73,6 +73,16 @@ describe('useStateList', () => { }); describe('setStateAt()', () => { + it('should not rerender when setting the current index', () => { + const hook = getHook(); + const renders = hook.result.all.length; + + act(() => hook.result.current.setStateAt(0)); + + expect(hook.result.current.state).toBe('a'); + expect(hook.result.all).toHaveLength(renders); + }); + it('should set state by it`s index in states list', () => { const hook = getHook(); @@ -109,6 +119,17 @@ describe('useStateList', () => { act(() => hook.result.current.setStateAt(-17)); expect(hook.result.current.state).toBe('b'); }); + + it.each([-3, -6, -9])('should wrap index %i to the first state', (index) => { + const hook = getHook(); + + act(() => hook.result.current.setStateAt(index)); + + expect(hook.result.current.currentIndex).toBe(0); + expect(hook.result.current.state).toBe('a'); + expect(hook.result.current.isFirst).toBe(true); + expect(hook.result.current.isLast).toBe(false); + }); }); describe('next()', () => { @@ -143,6 +164,17 @@ describe('useStateList', () => { }); describe('prev()', () => { + it('should keep the only state selected', () => { + const hook = getHook(['only']); + + act(() => hook.result.current.prev()); + + expect(hook.result.current.currentIndex).toBe(0); + expect(hook.result.current.state).toBe('only'); + expect(hook.result.current.isFirst).toBe(true); + expect(hook.result.current.isLast).toBe(true); + }); + it('on overflow should switch to last element (should be cycled)', () => { const hook = getHook(); @@ -177,6 +209,15 @@ describe('useStateList', () => { }); describe('with empty states list', () => { + it('should use an empty list by default and reject unknown states', () => { + const hook = renderHook(() => useStateList()); + + expect(hook.result.current.state).toBeUndefined(); + expect(() => hook.result.current.setState('a')).toThrow( + "State 'a' is not a valid state (does not exist in state list)" + ); + }); + it('should have `undefined` state', () => { expect(getHook([]).result.current.state).toBe(undefined); }); From 39b72c5ee71f30071dd12eb0b48c74ba5d91775b Mon Sep 17 00:00:00 2001 From: AMin Date: Sun, 25 Oct 2026 13:49:35 +0800 Subject: [PATCH 3/3] docs(useStateList): clarify negative index wrapping --- docs/useStateList.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/useStateList.md b/docs/useStateList.md index 21cf8f8d1b..bf7d7ed56f 100644 --- a/docs/useStateList.md +++ b/docs/useStateList.md @@ -49,6 +49,7 @@ If `stateSet` changed, became shorter than before and `currentIndex` left in shr - **`next()`**_`: void`_ — switches state to the next one. If last element selected it will switch to the first one; - **`setStateAt(newIndex: number)`**_`: void`_ — set the arbitrary state by index. Indexes are looped, and can be negative. _4ex:_ if list contains 5 elements, attempt to set index 9 will bring use to the 5th element, in case of negative index it will start counting from the right, so -17 will bring us to the 4th element. + Negative multiples of the list length wrap to the first element, so `setStateAt(-5)` selects index `0` in a five-element list. - **`setState(state: T)`**_`: void`_ — set the arbitrary state value that exists in `stateSet`. _In case new state does not exists in `stateSet` an Error will be thrown._ - **`isFirst`**_`: boolean`_ — `true` if current state is the first one. - **`isLast`**_`: boolean`_ — `true` if current state is the last one.