Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
119
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 98 lines 2.9 kB view raw
1import {useCallback, useEffect, useRef, useState} from 'react' 2 3type Task<TServerState> = { 4 isOn: boolean 5 resolve: (serverState: TServerState) => void 6 reject: (e: unknown) => void 7} 8 9type TaskQueue<TServerState> = { 10 activeTask: Task<TServerState> | null 11 queuedTask: Task<TServerState> | null 12} 13 14function AbortError() { 15 const e = new Error() 16 e.name = 'AbortError' 17 return e 18} 19 20export function useToggleMutationQueue<TServerState>({ 21 initialState, 22 runMutation, 23 onSuccess, 24}: { 25 initialState: TServerState 26 runMutation: ( 27 prevState: TServerState, 28 nextIsOn: boolean, 29 ) => Promise<TServerState> 30 onSuccess: (finalState: TServerState) => void 31}) { 32 // We use the queue as a mutable object. 33 // This is safe becuase it is not used for rendering. 34 const [queue] = useState<TaskQueue<TServerState>>({ 35 activeTask: null, 36 queuedTask: null, 37 }) 38 39 async function processQueue() { 40 if (queue.activeTask) { 41 // There is another active processQueue call iterating over tasks. 42 // It will handle any newly added tasks, so we should exit early. 43 return 44 } 45 // To avoid relying on the rendered state, capture it once at the start. 46 // From that point on, and until the queue is drained, we'll use the real server state. 47 let confirmedState: TServerState = initialState 48 try { 49 while (queue.queuedTask) { 50 const prevTask = queue.activeTask 51 const nextTask = queue.queuedTask 52 queue.activeTask = nextTask 53 queue.queuedTask = null 54 if (prevTask?.isOn === nextTask.isOn) { 55 // Skip multiple requests to update to the same value in a row. 56 prevTask.reject(new (AbortError as any)()) 57 continue 58 } 59 try { 60 // The state received from the server feeds into the next task. 61 // This lets us queue deletions of not-yet-created resources. 62 confirmedState = await runMutation(confirmedState, nextTask.isOn) 63 nextTask.resolve(confirmedState) 64 } catch (e) { 65 nextTask.reject(e) 66 } 67 } 68 } finally { 69 onSuccess(confirmedState) 70 queue.activeTask = null 71 queue.queuedTask = null 72 } 73 } 74 75 function queueToggle(isOn: boolean): Promise<TServerState> { 76 return new Promise((resolve, reject) => { 77 // This is a toggle, so the next queued value can safely replace the queued one. 78 if (queue.queuedTask) { 79 queue.queuedTask.reject(new (AbortError as any)()) 80 } 81 queue.queuedTask = {isOn, resolve, reject} 82 processQueue() 83 }) 84 } 85 86 const queueToggleRef = useRef(queueToggle) 87 useEffect(() => { 88 queueToggleRef.current = queueToggle 89 }) 90 const queueToggleStable = useCallback( 91 (isOn: boolean): Promise<TServerState> => { 92 const queueToggleLatest = queueToggleRef.current 93 return queueToggleLatest(isOn) 94 }, 95 [], 96 ) 97 return queueToggleStable 98}