-
-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathuse-list-state.ts
122 lines (102 loc) · 3.36 KB
/
use-list-state.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { useState } from 'react';
export interface UseListStateHandlers<T> {
setState: React.Dispatch<React.SetStateAction<T[]>>;
append: (...items: T[]) => void;
prepend: (...items: T[]) => void;
insert: (index: number, ...items: T[]) => void;
pop: () => void;
shift: () => void;
apply: (fn: (item: T, index?: number) => T) => void;
applyWhere: (
condition: (item: T, index: number) => boolean,
fn: (item: T, index?: number) => T
) => void;
remove: (...indices: number[]) => void;
reorder: ({ from, to }: { from: number; to: number }) => void;
swap: ({ from, to }: { from: number; to: number }) => void;
setItem: (index: number, item: T) => void;
setItemProp: <K extends keyof T, U extends T[K]>(index: number, prop: K, value: U) => void;
filter: (fn: (item: T, i: number) => boolean) => void;
}
export type UseListState<T> = [T[], UseListStateHandlers<T>];
export function useListState<T>(initialValue: T[] = []): UseListState<T> {
const [state, setState] = useState(initialValue);
const append = (...items: T[]) => setState((current) => [...current, ...items]);
const prepend = (...items: T[]) => setState((current) => [...items, ...current]);
const insert = (index: number, ...items: T[]) =>
setState((current) => [...current.slice(0, index), ...items, ...current.slice(index)]);
const apply = (fn: (item: T, index?: number) => T) =>
setState((current) => current.map((item, index) => fn(item, index)));
const remove = (...indices: number[]) =>
setState((current) => current.filter((_, index) => !indices.includes(index)));
const pop = () =>
setState((current) => {
const cloned = [...current];
cloned.pop();
return cloned;
});
const shift = () =>
setState((current) => {
const cloned = [...current];
cloned.shift();
return cloned;
});
const reorder = ({ from, to }: { from: number; to: number }) =>
setState((current) => {
const cloned = [...current];
const item = current[from];
cloned.splice(from, 1);
cloned.splice(to, 0, item);
return cloned;
});
const swap = ({ from, to }: { from: number; to: number }) =>
setState((current) => {
const cloned = [...current];
const fromItem = cloned[from];
const toItem = cloned[to];
cloned.splice(to, 1, fromItem);
cloned.splice(from, 1, toItem);
return cloned;
});
const setItem = (index: number, item: T) =>
setState((current) => {
const cloned = [...current];
cloned[index] = item;
return cloned;
});
const setItemProp = <K extends keyof T, U extends T[K]>(index: number, prop: K, value: U) =>
setState((current) => {
const cloned = [...current];
cloned[index] = { ...cloned[index], [prop]: value };
return cloned;
});
const applyWhere = (
condition: (item: T, index: number) => boolean,
fn: (item: T, index?: number) => T
) =>
setState((current) =>
current.map((item, index) => (condition(item, index) ? fn(item, index) : item))
);
const filter = (fn: (item: T, i: number) => boolean) => {
setState((current) => current.filter(fn));
};
return [
state,
{
setState,
append,
prepend,
insert,
pop,
shift,
apply,
applyWhere,
remove,
reorder,
swap,
setItem,
setItemProp,
filter,
},
];
}