Skip to content

Commit

Permalink
feature: add use-array hook
Browse files Browse the repository at this point in the history
  • Loading branch information
nico-lej-dev committed Mar 22, 2024
1 parent 07882a7 commit 55c103a
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/usehooks-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export * from './useTimeout'
export * from './useToggle'
export * from './useUnmount'
export * from './useWindowSize'
export * from './useArray'
1 change: 1 addition & 0 deletions packages/usehooks-ts/src/useArray/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useArray'
16 changes: 16 additions & 0 deletions packages/usehooks-ts/src/useArray/useArray.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useArray } from './useArray'

export default function Component() {
const { array, length, addOne, removeOne } = useArray<number>()

return <div>
{
array.map((value, index) => <div>
<p>Value: {value}</p>
<button onClick={() => removeOne(index)} />
</div>
)
}
<button onClick={() => addOne(length)}/>
</div>
}
3 changes: 3 additions & 0 deletions packages/usehooks-ts/src/useArray/useArray.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
An abstraction to play with an array.

This hook provides utility functions to handle an array.
234 changes: 234 additions & 0 deletions packages/usehooks-ts/src/useArray/useArray.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { act, renderHook } from '@testing-library/react'

import { useArray } from './useArray'

describe('useArray()', () => {
it('should use the default array', () => {
const { result } = renderHook(() => useArray())
const { array, length } = result.current

expect(array).toStrictEqual([])
expect(length).toBe(0)
})

it('should use the given array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))
const { array, length } = result.current

expect(array).toStrictEqual(['a', 'b', 'c'])
expect(length).toBe(3)
})

it('should add one element in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.addOne('d')
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c', 'd'])
expect(result.current.length).toBe(4)
})

it('should add many elements in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.addMany(['d', 'e'])
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c', 'd', 'e'])
expect(result.current.length).toBe(5)
})

it('should setOne element in the array (updateOne)', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.setOne({ index: 0, change: 'd' })
})

expect(result.current.array).toStrictEqual(['d', 'b', 'c'])
expect(result.current.length).toBe(3)
})

it('should setOne element in the array (addOne)', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.setOne({ index: 4, change: 'd' })
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c', 'd'])
expect(result.current.length).toBe(4)
})

it('should setMany elements in the array (update)', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.setMany([{ index: 0, change: 'd' }, { index: 1, change: 'e' }])
})

expect(result.current.array).toStrictEqual(['d', 'e', 'c'])
expect(result.current.length).toBe(3)
})

it('should setMany elements in the array (add)', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.setMany([{ index: 4, change: 'd' }, { index: 5, change: 'e' }])
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c', 'd', 'e'])
expect(result.current.length).toBe(5)
})

it('should setMany elements in the array (add and update)', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.setMany([{ index: 0, change: 'd' }, { index: 4, change: 'e' }])
})

expect(result.current.array).toStrictEqual(['d', 'b', 'c', 'e'])
expect(result.current.length).toBe(4)
})

it('should setAll elements in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.setAll(['d', 'e'])
})

expect(result.current.array).toStrictEqual(['d', 'e'])
expect(result.current.length).toBe(2)
})

it('should removeOne element in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.removeOne(0)
})

expect(result.current.array).toStrictEqual(['b', 'c'])
expect(result.current.length).toBe(2)
})

it('should not removeOne element in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.removeOne(3)
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c'])
expect(result.current.length).toBe(3)
})

it('should removeMany elements in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.removeMany([0, 1])
})

expect(result.current.array).toStrictEqual(['c'])
expect(result.current.length).toBe(1)
})

it('should not removeMany elements in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.removeMany([3, 4])
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c'])
expect(result.current.length).toBe(3)
})

it('should removeAll elements in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.removeAll()
})

expect(result.current.array).toStrictEqual([])
expect(result.current.length).toBe(0)
})

it('should updateOne element in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.updateOne({ index: 0, change: 'd' })
})

expect(result.current.array).toStrictEqual(['d', 'b', 'c'])
expect(result.current.length).toBe(3)
})

it('should not updateOne element in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.updateOne({ index: 5, change: 'd' })
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c'])
expect(result.current.length).toBe(3)
})

it('should updateMany elements in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.updateMany([{ index: 0, change: 'd' }, { index: 1, change: 'e' }])
})

expect(result.current.array).toStrictEqual(['d', 'e', 'c'])
expect(result.current.length).toBe(3)
})

it('should reset the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.addMany(['d', 'e'])
result.current.reset()
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c'])
expect(result.current.length).toBe(3)
})

it('should not updateMany element in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

act(() => {
result.current.updateMany([{ index: 4, change: 'd' }, { index: 5, change: 'e' }])
})

expect(result.current.array).toStrictEqual(['a', 'b', 'c'])
expect(result.current.length).toBe(3)
})

it('should select elements in the array', () => {
const { result } = renderHook(() => useArray(['a', 'b', 'c']))

expect(result.current.select()).toStrictEqual(['a', 'b', 'c'])

expect(result.current.select((element) => element === 'a')).toStrictEqual(['a'])
expect(result.current.some((element) => element === 'a')).toBeTruthy()

expect(result.current.select((element) => element === 'd')).toStrictEqual([])
expect(result.current.some((element) => element === 'd')).toBeFalsy()
})

})

120 changes: 120 additions & 0 deletions packages/usehooks-ts/src/useArray/useArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useCallback, useMemo, useState } from 'react'

import type { Dispatch, SetStateAction } from 'react'

/**
* Represents the type for the update of an element at a specific index in the array.
*/
type Update<T> = { index: number, change: T }

/** The useArray return type. */
type UseArrayReturnType<T> = {
/** The current array state value. */
array: Array<T>
/** Function to set the array state directly. */
setArray: Dispatch<SetStateAction<Array<T>>>
/** Length of the array state value. */
length: number
/** Function to add one element in the array state value. */
addOne: (element: T) => void
/** Function to add many elements in the array state value. */
addMany: (elements: Array<T>) => void
/** Function to update or add one element in the array state value. */
setOne: (element: Update<T>) => void
/** Function to update or add many elements in the array state value. */
setMany: (elements: Array<Update<T>>) => void
/** Function to replace all elements in the array state value.*/
setAll: (elements: Array<T>) => void
/** Function to remove one element in the array state value. */
removeOne: (index: number) => void
/** Function to remove many elements in the array state value. */
removeMany: (indexes: Array<number>) => void
/** Function to remove all elements in the array state value. */
removeAll: () => void
/** Function to update one element in the array state value. */
updateOne: (element: Update<T>) => void
/** Function to update many elements in the array state value. */
updateMany: (elements: Array<Update<T>>) => void
/** Function to reset the array state value with the feault value. */
reset: () => void
/** Function to return some elements of the array state value. */
select: (predicate?: (element: T, index: number, array: Array<T>) => unknown) => Array<T>
/** Function to return determine if elements of the array state value are presents or not. */
some: (predicate: (element: T, index: number, array: Array<T>) => unknown) => Boolean
}

/**
* Custom hook that handles array state with useful utility functions.
* @param {Array<T>} [defaultArray] - The initial value for the array state (default is `[]`).
* @returns {UseArrayReturnType} An object containing the array state value and utility functions to manipulate the state.
* @see [Documentation](https://usehooks-ts.com/react-hook/use-array)
* @example
* ```tsx
* const { array, setArray } = useArray<string>(['a', 'b', 'c']);
*
* console.log(value); // ['a', 'b', 'c']
* ```
*/
export function useArray<T>(
defaultArray?: Array<T>,
): UseArrayReturnType<T> {
const [array, setArray] = useState(defaultArray || [])
const length = useMemo(() => array.length, [array])

const addOne = useCallback((element: T) => setArray([...array, element]), [array, setArray])

const addMany = useCallback((elements: Array<T>) => setArray([...array, ...elements]), [array, setArray])

const removeOne = useCallback((index: number) => setArray(array.filter((_, i) => i !== index)), [array, setArray])

const removeMany = useCallback((indexes: Array<number>) => setArray(array.filter((_, i) => !indexes.includes(i))), [array, setArray])

const removeAll = useCallback(() => setArray([]), [setArray])

const updateOne = useCallback(({ index, change}: Update<T>) => setArray(array.map((e, i) => index === i ? change : e)), [array, setArray])

const updateMany = useCallback((elements: Array<Update<T>>) => {
const newArray = [...array]
elements.forEach(({ index, change }) => {
if (!!array[index]) {
newArray[index] = change
}
})
setArray(newArray)
}, [array, setArray])

const setOne = useCallback(({ index, change }: Update<T>) => !!array[index] ? updateOne({ index, change }) : addOne(change), [array, updateOne, addOne])

const setMany = useCallback((elements: Array<Update<T>>) => {
const newArray = [...array]
elements.forEach(({ index, change }) => !!array[index] ? newArray[index] = change : newArray.push(change))
setArray(newArray)
}, [array, setArray])

const setAll = useCallback((elements: Array<T>) => setArray(elements), [setArray])

const reset = useCallback(() => setArray(defaultArray || []), [])

const select = useCallback((predicate?: (element: T, index: number, array: Array<T>) => unknown) => !!predicate ? array.filter(predicate) : array, [array])

const some = useCallback((predicate: (element: T, index: number, array: Array<T>) => unknown) => array.some(predicate), [array])

return {
array,
length,
setArray,
addOne,
addMany,
setOne,
setMany,
setAll,
removeOne,
removeMany,
removeAll,
updateOne,
updateMany,
reset,
select,
some
}
}

0 comments on commit 55c103a

Please sign in to comment.