-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
226 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# `useStateList` | ||
|
||
React state hook that circularly iterates over an array. | ||
|
||
## Usage | ||
|
||
```jsx | ||
import { useStateList } from 'react-use'; | ||
|
||
const stateSet = ['first', 'second', 'third', 'fourth', 'fifth']; | ||
|
||
const Demo = () => { | ||
const {state, prev, next} = useStateList(stateSet); | ||
|
||
return ( | ||
<div> | ||
<pre>{state}</pre> | ||
<button onClick={() => prev()}>prev</button> | ||
<button onClick={() => next()}>next</button> | ||
</div> | ||
); | ||
}; | ||
``` | ||
|
||
> If the `stateSet` is changed by a shorter one the hook will select the last element of it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { storiesOf } from '@storybook/react'; | ||
import * as React from 'react'; | ||
import { useStateList } from '..'; | ||
import ShowDocs from './util/ShowDocs'; | ||
|
||
const stateSet = ['first', 'second', 'third', 'fourth', 'fifth']; | ||
|
||
const Demo = () => { | ||
const { state, prev, next } = useStateList(stateSet); | ||
|
||
return ( | ||
<div> | ||
<pre>{state}</pre> | ||
<button onClick={() => prev()}>prev</button> | ||
<button onClick={() => next()}>next</button> | ||
</div> | ||
); | ||
}; | ||
|
||
storiesOf('State|useStateList', module) | ||
.add('Docs', () => <ShowDocs md={require('../../docs/useStateList.md')} />) | ||
.add('Demo', () => <Demo />); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { renderHook, act } from '@testing-library/react-hooks'; | ||
import useStateList from '../useStateList'; | ||
|
||
const callNext = hook => { | ||
act(() => { | ||
const { next } = hook.result.current; | ||
next(); | ||
}); | ||
}; | ||
|
||
const callPrev = hook => { | ||
act(() => { | ||
const { prev } = hook.result.current; | ||
prev(); | ||
}); | ||
}; | ||
|
||
describe('happy flow', () => { | ||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), { | ||
initialProps: { | ||
stateSet: ['a', 'b', 'c'], | ||
}, | ||
}); | ||
|
||
it('should return the first state on initial render', () => { | ||
const { state } = hook.result.current; | ||
expect(state).toBe('a'); | ||
}); | ||
|
||
it('should return the second state after calling the "next" function', () => { | ||
callNext(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('b'); | ||
}); | ||
|
||
it('should return the first state again after calling the "next" function "stateSet.length" times', () => { | ||
callNext(hook); | ||
callNext(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('a'); | ||
}); | ||
|
||
it('should return the last state again after calling the "prev" function', () => { | ||
callPrev(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('c'); | ||
}); | ||
|
||
it('should return the previous state after calling the "prev" function', () => { | ||
callPrev(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('b'); | ||
}); | ||
}); | ||
|
||
describe('with empty state set', () => { | ||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), { | ||
initialProps: { | ||
stateSet: [], | ||
}, | ||
}); | ||
|
||
it('should return undefined on initial render', () => { | ||
const { state } = hook.result.current; | ||
expect(state).toBe(undefined); | ||
}); | ||
|
||
it('should always return undefined (calling next)', () => { | ||
callNext(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe(undefined); | ||
}); | ||
|
||
it('should always return undefined (calling prev)', () => { | ||
callPrev(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe(undefined); | ||
}); | ||
}); | ||
|
||
describe('with a single state set', () => { | ||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), { | ||
initialProps: { | ||
stateSet: ['a'], | ||
}, | ||
}); | ||
|
||
it('should return "a" on initial render', () => { | ||
const { state } = hook.result.current; | ||
expect(state).toBe('a'); | ||
}); | ||
|
||
it('should always return "a" (calling next)', () => { | ||
callNext(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('a'); | ||
}); | ||
|
||
it('should always return "a" (calling prev)', () => { | ||
callPrev(hook); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('a'); | ||
}); | ||
}); | ||
|
||
describe('with stateSet updates', () => { | ||
const hook = renderHook(({ stateSet }) => useStateList(stateSet), { | ||
initialProps: { | ||
stateSet: ['a', 'c', 'b', 'f', 'g'], | ||
}, | ||
}); | ||
|
||
it('should return the last element after updating with a shorter state set', () => { | ||
// Go to the 4th state | ||
callNext(hook); // c | ||
callNext(hook); // b | ||
callNext(hook); // f | ||
|
||
// Update the state set with less elements | ||
hook.rerender({ | ||
stateSet: ['a', 'c'], | ||
}); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('c'); | ||
}); | ||
|
||
it('should return the element in the same position after updating with a larger state set', () => { | ||
hook.rerender({ | ||
stateSet: ['a', 'f', 'l'], | ||
}); | ||
|
||
const { state } = hook.result.current; | ||
expect(state).toBe('f'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { useState, useCallback } from 'react'; | ||
|
||
import useUpdateEffect from './useUpdateEffect'; | ||
|
||
export default function useStateList<T>(stateSet: T[] = []): { state: T; next: () => void; prev: () => void } { | ||
const [currentIndex, setCurrentIndex] = useState(0); | ||
|
||
// In case we receive a different state set, check if the current index still exists and | ||
// reset it to the last if it don't. | ||
useUpdateEffect(() => { | ||
if (!stateSet[currentIndex]) { | ||
setCurrentIndex(stateSet.length - 1); | ||
} | ||
}, [stateSet]); | ||
|
||
const next = useCallback(() => { | ||
const nextStateIndex = stateSet.length === currentIndex + 1 ? 0 : currentIndex + 1; | ||
|
||
setCurrentIndex(nextStateIndex); | ||
}, [stateSet, currentIndex]); | ||
|
||
const prev = useCallback(() => { | ||
const prevStateIndex = currentIndex === 0 ? stateSet.length - 1 : currentIndex - 1; | ||
|
||
setCurrentIndex(prevStateIndex); | ||
}, [stateSet, currentIndex]); | ||
|
||
return { | ||
state: stateSet[currentIndex], | ||
next, | ||
prev, | ||
}; | ||
} |