Skip to content

Instantly share code, notes, and snippets.

@nilscox
Created May 11, 2023 09:39
Show Gist options
  • Save nilscox/d2cafbf75c018599abe2ce23be295e90 to your computer and use it in GitHub Desktop.
Save nilscox/d2cafbf75c018599abe2ce23be295e90 to your computer and use it in GitHub Desktop.
React hook to handle a list of items
import { act, renderHook } from '@testing-library/react'
import { useList } from './use-list'
describe('useList', () => {
it('creates an empty list', () => {
const { result } = renderHook(() => useList())
expect(result.current[0]).toEqual([])
})
it('creates a list from existing items', () => {
const { result } = renderHook(() => useList([1, 2]))
expect(result.current[0]).toEqual([1, 2])
})
it('adds a item to the list', () => {
const { result } = renderHook(() => useList<number>())
act(() => result.current[1].add(1))
expect(result.current[0]).toEqual([1])
act(() => result.current[1].add(2))
expect(result.current[0]).toEqual([1, 2])
})
it('removes items from the list', () => {
const { result } = renderHook(() => useList([1, 2]))
act(() => result.current[1].remove(1))
expect(result.current[0]).toEqual([2])
act(() => result.current[1].remove(2))
expect(result.current[0]).toEqual([])
})
it('adds an item or removes it if it already exists', () => {
const { result } = renderHook(() => useList<number>([]))
act(() => result.current[1].toggle(1))
expect(result.current[0]).toEqual([1])
act(() => result.current[1].toggle(1))
expect(result.current[0]).toEqual([])
})
})
import { Reducer, useReducer, useMemo } from 'react'
type UseListReturn<T> = [
T[],
{
add: (item: T) => void
remove: (item: T) => void
toggle: (item: T) => void
}
]
export const useList = <T,>(initial: T[] = []): UseListReturn<T> => {
const [list, dispatch] = useReducer(reducer as ListReducer<T>, initial)
const actions = useMemo<UseListReturn<T>[1]>(
() => ({
add: (item) => dispatch({ type: 'add', item }),
remove: (item) => dispatch({ type: 'remove', item }),
toggle: (item) => dispatch({ type: 'toggle', item }),
}),
[dispatch]
)
return [list, actions]
}
type ListReducer<T> = Reducer<T[], ListAction<T>>
type ListAction<T> = {
type: 'add' | 'remove' | 'toggle'
item: T
}
const reducer: ListReducer<unknown> = (state, { type, item }) => {
if (type === 'add') {
return [...state, item]
}
if (type === 'remove') {
const index = state.indexOf(item)
if (index < 0) {
return state
}
return [...state.slice(0, index), ...state.slice(index + 1)]
}
if (type === 'toggle') {
const result = reducer(state, { type: 'remove', item })
if (result !== state) {
return result
}
return reducer(state, { type: 'add', item })
}
return state
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment