Repository: streamich/react-use
Pull request #2537 (2024-01-22 10:17:22)

feat(pencil): add isFirst and isLast return value to 'useStateList'
# Description

<!-- Please include a summary of the change along with relevant motivation and context. -->
I wanted to directly add `isFirst` and `isLast` to the return props. This way, I won't need to use `currentIndex == array.length`, and I'll have them available directly in the return state props.

## Type of change

- [ ] Bug fix _(non-breaking change which fixes an issue)_
- [X] New feature _(non-breaking change which adds functionality)_
- [ ] **Breaking change** _(fix or feature that would cause existing functionality to not work as before)_

# Checklist
- [X] Read the [Contributing Guide](https://github.com/streamich/react-use/blob/master/CONTRIBUTING.md)
- [X] Perform a code self-review
- [/] Comment the code, particularly in hard-to-understand areas
- [X] Add documentation
- [X] Add hook's story at Storybook
- [X] Cover changes with tests
- [X] Ensure the test suite passes (`yarn test`)
- [X] Provide 100% tests coverage
- [X] Make sure code lints (`yarn lint`). Fix it with `yarn lint:fix` in case of failure. (mine is okay but i have error for other files)
- [X] Make sure types are fine (`yarn lint:types`).



Patch:
diff --git a/docs/useStateList.md b/docs/useStateList.md
index d6e03e0a..21cf8f8d 100644
--- a/docs/useStateList.md
+++ b/docs/useStateList.md
@@ -12,14 +12,14 @@ import { useRef } from 'react';
 const stateSet = ['first', 'second', 'third', 'fourth', 'fifth'];
 
 const Demo = () => {
-  const { state, prev, next, setStateAt, setState, currentIndex } = useStateList(stateSet);
+  const { state, prev, next, setStateAt, setState, currentIndex, isFirst, isLast } = useStateList(stateSet);
   const indexInput = useRef<HTMLInputElement>(null);
   const stateInput = useRef<HTMLInputElement>(null);
 
   return (
     <div>
       <pre>
-        {state} [index: {currentIndex}]
+        {state} [index: {currentIndex}], [isFirst: {isFirst}], [isLast: {isLast}]
       </pre>
       <button onClick={() => prev()}>prev</button>
       <br />
@@ -38,7 +38,7 @@ const Demo = () => {
 ## Reference
 
 ```ts
-const { state, currentIndex, prev, next, setStateAt, setState } = useStateList<T>(stateSet: T[] = []);
+const { state, currentIndex, prev, next, setStateAt, setState, isFirst, isLast } = useStateList<T>(stateSet: T[] = []);
 ```
 
 If `stateSet` changed, became shorter than before and `currentIndex` left in shrunk gap - the last element of list will be taken as current.
@@ -48,5 +48,7 @@ If `stateSet` changed, became shorter than before and `currentIndex` left in shr
 - **`prev()`**_`: void`_ &mdash; switches state to the previous one. If first element selected it will switch to the last one;
 - **`next()`**_`: void`_ &mdash; switches state to the next one. If last element selected it will switch to the first one;
 - **`setStateAt(newIndex: number)`**_`: void`_ &mdash; 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.
+  _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.
 - **`setState(state: T)`**_`: void`_ &mdash; 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`_ &mdash; `true` if current state is the first one.
+- **`isLast`**_`: boolean`_ &mdash; `true` if current state is the last one.
diff --git a/src/useStateList.ts b/src/useStateList.ts
index 7e4d605c..748e317c 100644
--- a/src/useStateList.ts
+++ b/src/useStateList.ts
@@ -10,6 +10,8 @@ export interface UseStateListReturn<T> {
   setState: (state: T) => void;
   next: () => void;
   prev: () => void;
+  isFirst: boolean;
+  isLast: boolean;
 }
 
 export default function useStateList<T>(stateSet: T[] = []): UseStateListReturn<T> {
@@ -68,6 +70,8 @@ export default function useStateList<T>(stateSet: T[] = []): UseStateListReturn<
   return {
     state: stateSet[index.current],
     currentIndex: index.current,
+    isFirst: index.current === 0,
+    isLast: index.current === stateSet.length - 1,
     ...actions,
   };
 }
diff --git a/stories/useStateList.story.tsx b/stories/useStateList.story.tsx
index c33e1a2f..938eb0d5 100644
--- a/stories/useStateList.story.tsx
+++ b/stories/useStateList.story.tsx
@@ -7,14 +7,15 @@ import ShowDocs from './util/ShowDocs';
 const stateSet = ['first', 'second', 'third', 'fourth', 'fifth'];
 
 const Demo = () => {
-  const { state, prev, next, setStateAt, setState, currentIndex } = useStateList(stateSet);
+  const { state, prev, next, setStateAt, setState, currentIndex, isFirst, isLast } =
+    useStateList(stateSet);
   const indexInput = useRef<HTMLInputElement>(null);
   const stateInput = useRef<HTMLInputElement>(null);
 
   return (
     <div>
       <pre>
-        {state} [index: {currentIndex}]
+        {state} [index: {currentIndex}], [isFirst: {isFirst}], [isLast: {isLast}]
       </pre>
       <button onClick={() => prev()}>prev</button>
       <br />


Test patch:
diff --git a/tests/useStateList.test.ts b/tests/useStateList.test.ts
index 95cb6f03..21e56410 100644
--- a/tests/useStateList.test.ts
+++ b/tests/useStateList.test.ts
@@ -20,6 +20,8 @@ describe('useStateList', () => {
       next: expect.any(Function),
       setStateAt: expect.any(Function),
       setState: expect.any(Function),
+      isFirst: expect.any(Boolean),
+      isLast: expect.any(Boolean),
     });
   });
 
@@ -27,6 +29,17 @@ describe('useStateList', () => {
     expect(getHook().result.current.state).toBe('a');
   });
 
+  it('should return isFirst on init', () => {
+    expect(getHook().result.current.isFirst).toBe(true);
+  });
+
+  it('should return isLast when on last state', () => {
+    const hook = getHook();
+    act(() => hook.result.current.setStateAt(2));
+
+    expect(hook.result.current.isLast).toBe(true);
+  });
+
   describe('setState()', () => {
     it('should set state value if it exists in states list', () => {
       const hook = getHook();