-
Notifications
You must be signed in to change notification settings - Fork 125
/
useStateWithHistory.js
48 lines (42 loc) · 1.71 KB
/
useStateWithHistory.js
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
import { useCallback, useRef, useState } from 'react'
// sets a value and updates the history array
// (mutates the history array in place; returns the new index)
function setWithHistory(value, history, i, capacity) {
if (i < 0 || i > history.length - 1) return i // ignore if out of bounds
if (history[i] === value) return i // no change if value is the same
if (i < history.length - 1) history.splice(i + 1) // erase the future if changing the past
history.push(value) // add the new value to the history
while (history.length > capacity) history.shift() // cap the history at the capacity
return history.length - 1 // return the new index
}
export default function useStateWithHistory(defaultValue, { capacity = 10 } = {}) {
const [value, setValue] = useState(defaultValue)
const historyRef = useRef([value])
const pointerRef = useRef(0)
const set = useCallback(
v => {
const resolvedValue = typeof v === 'function' ? v(value) : v
pointerRef.current = setWithHistory(resolvedValue, historyRef.current, pointerRef.current, capacity)
setValue(resolvedValue)
},
[capacity, value]
)
const go = useCallback(index => {
if (index < 0 || index > historyRef.current.length - 1) return
pointerRef.current = index
setValue(historyRef.current[pointerRef.current])
}, [])
const forward = useCallback(() => go(pointerRef.current + 1), [go])
const back = useCallback(() => go(pointerRef.current - 1), [go])
return [
value,
set,
{
history: historyRef.current,
pointer: pointerRef.current,
back,
forward,
go
}
]
}