reuse debounce hook more places
This commit is contained in:
		
							parent
							
								
									0a35bca650
								
							
						
					
					
						commit
						8ca8bb985e
					
				| @ -2,8 +2,6 @@ import { gql, useMutation } from '@apollo/client' | ||||
| import Dropdown from 'react-bootstrap/Dropdown' | ||||
| import { useShowModal } from './modal' | ||||
| import { useToast } from './toast' | ||||
| import { InvoiceModal, payOrLoginError } from './invoice' | ||||
| import { DONT_LIKE_THIS_COST } from '../lib/constants' | ||||
| import ItemAct from './item-act' | ||||
| 
 | ||||
| export default function DontLikeThisDropdownItem ({ id }) { | ||||
| @ -40,23 +38,8 @@ export default function DontLikeThisDropdownItem ({ id }) { | ||||
|               }} itemId={id} act={dontLikeThis} down | ||||
|             />) | ||||
|         } catch (error) { | ||||
|           console.error(error) | ||||
|           if (payOrLoginError(error)) { | ||||
|             showModal(onClose => { | ||||
|               return ( | ||||
|                 <InvoiceModal | ||||
|                   amount={DONT_LIKE_THIS_COST} | ||||
|                   onPayment={async ({ hash, hmac }) => { | ||||
|                     await dontLikeThis({ variables: { id, hash, hmac } }) | ||||
|                     toaster.success('item flagged') | ||||
|                   }} | ||||
|                 /> | ||||
|               ) | ||||
|             }) | ||||
|           } else { | ||||
|           toaster.danger('failed to flag item') | ||||
|         } | ||||
|         } | ||||
|       }} | ||||
|     > | ||||
|       flag | ||||
|  | ||||
| @ -149,6 +149,9 @@ const defaultOptions = { | ||||
|   callback: null, // (formValues) => void
 | ||||
|   replaceModal: false | ||||
| } | ||||
| // TODO: refactor this so it can be easily understood
 | ||||
| // there's lots of state cascading paired with logic
 | ||||
| // independent of the state, and it's hard to follow
 | ||||
| export const useInvoiceable = (onSubmit, options = defaultOptions) => { | ||||
|   const me = useMe() | ||||
|   const [createInvoice, { data }] = useMutation(gql` | ||||
| @ -247,17 +250,14 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => { | ||||
|     // tell onSubmit handler that we want to keep local storage
 | ||||
|     // even though the submit handler was "successful"
 | ||||
|     return { keepLocalStorage: true } | ||||
|   }, [onSubmit, setFormValues, setSubmitArgs, createInvoice]) | ||||
|   }, [onSubmit, setFormValues, setSubmitArgs, createInvoice, !!me]) | ||||
| 
 | ||||
|   return onSubmitWrapper | ||||
| } | ||||
| 
 | ||||
| export const InvoiceModal = ({ onPayment, amount }) => { | ||||
|   const createInvoice = useInvoiceable(onPayment, { replaceModal: true }) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     createInvoice({ amount }) | ||||
|   }, []) | ||||
| export const useInvoiceModal = (onPayment, deps) => { | ||||
|   const onPaymentMemo = useCallback(onPayment, deps) | ||||
|   return useInvoiceable(onPaymentMemo, { replaceModal: true }) | ||||
| } | ||||
| 
 | ||||
| export const payOrLoginError = (error) => { | ||||
|  | ||||
| @ -67,7 +67,7 @@ export default function ItemAct ({ onClose, itemId, act, down, strike }) { | ||||
|   return ( | ||||
|     <Form | ||||
|       initial={{ | ||||
|         amount: me?.tipDefault, | ||||
|         amount: me?.tipDefault || defaultTips[0], | ||||
|         default: false | ||||
|       }} | ||||
|       schema={amountSchema} | ||||
|  | ||||
| @ -7,7 +7,7 @@ import { useMe } from './me' | ||||
| import { numWithUnits } from '../lib/format' | ||||
| import { useShowModal } from './modal' | ||||
| import { useRoot } from './root' | ||||
| import { InvoiceModal, payOrLoginError } from './invoice' | ||||
| import { payOrLoginError, useInvoiceModal } from './invoice' | ||||
| 
 | ||||
| export default function PayBounty ({ children, item }) { | ||||
|   const me = useMe() | ||||
| @ -59,6 +59,9 @@ export default function PayBounty ({ children, item }) { | ||||
|       } | ||||
|     } | ||||
|   ) | ||||
|   const showInvoiceModal = useInvoiceModal(async ({ hash, hmac }, { variables }) => { | ||||
|     await act({ variables: { ...variables, hash, hmac } }) | ||||
|   }, [act]) | ||||
| 
 | ||||
|   const handlePayBounty = async onComplete => { | ||||
|     try { | ||||
| @ -74,16 +77,7 @@ export default function PayBounty ({ children, item }) { | ||||
|       onComplete() | ||||
|     } catch (error) { | ||||
|       if (payOrLoginError(error)) { | ||||
|         showModal(onClose => { | ||||
|           return ( | ||||
|             <InvoiceModal | ||||
|               amount={root.bounty} | ||||
|               onPayment={async ({ hash, hmac }) => { | ||||
|                 await act({ variables: { id: item.id, sats: root.bounty, hash, hmac } }) | ||||
|               }} | ||||
|             /> | ||||
|           ) | ||||
|         }) | ||||
|         showInvoiceModal({ amount: root.bounty }, { variables: { id: item.id, sats: root.bounty } }) | ||||
|         return | ||||
|       } | ||||
|       throw new Error({ message: error.toString() }) | ||||
|  | ||||
| @ -7,13 +7,11 @@ import styles from './poll.module.css' | ||||
| import Check from '../svgs/checkbox-circle-fill.svg' | ||||
| import { signIn } from 'next-auth/react' | ||||
| import ActionTooltip from './action-tooltip' | ||||
| import { useShowModal } from './modal' | ||||
| import { POLL_COST } from '../lib/constants' | ||||
| import { InvoiceModal } from './invoice' | ||||
| import { payOrLoginError, useInvoiceModal } from './invoice' | ||||
| 
 | ||||
| export default function Poll ({ item }) { | ||||
|   const me = useMe() | ||||
|   const showModal = useShowModal() | ||||
|   const [pollVote] = useMutation( | ||||
|     gql` | ||||
|       mutation pollVote($id: ID!, $hash: String, $hmac: String) { | ||||
| @ -47,6 +45,12 @@ export default function Poll ({ item }) { | ||||
|   ) | ||||
| 
 | ||||
|   const PollButton = ({ v }) => { | ||||
|     const showInvoiceModal = useInvoiceModal(async ({ hash, hmac }, { variables }) => { | ||||
|       await pollVote({ variables: { ...variables, hash, hmac } }) | ||||
|     }, [pollVote]) | ||||
| 
 | ||||
|     const variables = { id: v.id } | ||||
| 
 | ||||
|     return ( | ||||
|       <ActionTooltip placement='left' notForm> | ||||
|         <Button | ||||
| @ -55,22 +59,17 @@ export default function Poll ({ item }) { | ||||
|             ? async () => { | ||||
|               try { | ||||
|                 await pollVote({ | ||||
|                   variables: { id: v.id }, | ||||
|                   variables, | ||||
|                   optimisticResponse: { | ||||
|                     pollVote: v.id | ||||
|                   } | ||||
|                 }) | ||||
|               } catch (error) { | ||||
|                 showModal(onClose => { | ||||
|                   return ( | ||||
|                     <InvoiceModal | ||||
|                       amount={item.pollCost || POLL_COST} | ||||
|                       onPayment={async ({ hash, hmac }) => { | ||||
|                         await pollVote({ variables: { id: v.id, hash, hmac } }) | ||||
|                       }} | ||||
|                     /> | ||||
|                   ) | ||||
|                 }) | ||||
|                 if (payOrLoginError(error)) { | ||||
|                   showInvoiceModal({ amount: item.pollCost || POLL_COST }, { variables }) | ||||
|                   return | ||||
|                 } | ||||
|                 throw new Error({ message: error.toString() }) | ||||
|               } | ||||
|             } | ||||
|             : signIn} | ||||
|  | ||||
| @ -5,14 +5,15 @@ import ActionTooltip from './action-tooltip' | ||||
| import ItemAct from './item-act' | ||||
| import { useMe } from './me' | ||||
| import Rainbow from '../lib/rainbow' | ||||
| import { useCallback, useEffect, useMemo, useRef, useState } from 'react' | ||||
| import { useCallback, useMemo, useRef, useState } from 'react' | ||||
| import LongPressable from 'react-longpressable' | ||||
| import Overlay from 'react-bootstrap/Overlay' | ||||
| import Popover from 'react-bootstrap/Popover' | ||||
| import { useShowModal } from './modal' | ||||
| import { LightningConsumer, useLightning } from './lightning' | ||||
| import { numWithUnits } from '../lib/format' | ||||
| import { InvoiceModal, payOrLoginError } from './invoice' | ||||
| import { payOrLoginError, useInvoiceModal } from './invoice' | ||||
| import useDebounceCallback from './use-debounce-callback' | ||||
| 
 | ||||
| const getColor = (meSats) => { | ||||
|   if (!meSats || meSats <= 10) { | ||||
| @ -69,7 +70,6 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats } | ||||
|   const [voteShow, _setVoteShow] = useState(false) | ||||
|   const [tipShow, _setTipShow] = useState(false) | ||||
|   const ref = useRef() | ||||
|   const timerRef = useRef(null) | ||||
|   const me = useMe() | ||||
|   const strike = useLightning() | ||||
|   const [setWalkthrough] = useMutation( | ||||
| @ -153,21 +153,17 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats } | ||||
|       } | ||||
|     } | ||||
|   ) | ||||
|   const showInvoiceModal = useInvoiceModal( | ||||
|     async ({ hash, hmac }, { variables }) => { | ||||
|       await act({ variables: { ...variables, hash, hmac } }) | ||||
|       strike() | ||||
|     }, [act, strike]) | ||||
| 
 | ||||
|   // if we want to use optimistic response, we need to buffer the votes
 | ||||
|   // because if someone votes in quick succession, responses come back out of order
 | ||||
|   // so we wait a bit to see if there are more votes coming in
 | ||||
|   // this effectively performs our own debounced optimistic response
 | ||||
|   useEffect(() => { | ||||
|     if (timerRef.current) { | ||||
|       clearTimeout(timerRef.current) | ||||
|     } | ||||
| 
 | ||||
|     if (pendingSats > 0) { | ||||
|       timerRef.current = setTimeout(async (sats) => { | ||||
|         const variables = { id: item.id, sats: pendingSats } | ||||
|   const zap = useDebounceCallback(async (sats) => { | ||||
|     if (!sats) return | ||||
|     const variables = { id: item.id, sats } | ||||
|     try { | ||||
|           timerRef.current && setPendingSats(0) | ||||
|       setPendingSats(0) | ||||
|       await act({ | ||||
|         variables, | ||||
|         optimisticResponse: { | ||||
| @ -178,30 +174,12 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats } | ||||
|       }) | ||||
|     } catch (error) { | ||||
|       if (payOrLoginError(error)) { | ||||
|             showModal(onClose => { | ||||
|               return ( | ||||
|                 <InvoiceModal | ||||
|                   amount={pendingSats} | ||||
|                   onPayment={async ({ hash, hmac }) => { | ||||
|                     await act({ variables: { ...variables, hash, hmac } }) | ||||
|                     strike() | ||||
|                   }} | ||||
|                 /> | ||||
|               ) | ||||
|             }) | ||||
|         showInvoiceModal({ amount: sats }, { variables }) | ||||
|         return | ||||
|       } | ||||
|           if (!timerRef.current) return | ||||
|       throw new Error({ message: error.toString() }) | ||||
|     } | ||||
|       }, 500, pendingSats) | ||||
|     } | ||||
| 
 | ||||
|     return async () => { | ||||
|       clearTimeout(timerRef.current) | ||||
|       timerRef.current = null | ||||
|     } | ||||
|   }, [pendingSats, act, item, showModal, setPendingSats]) | ||||
|   }, 500, [act, item?.id, showInvoiceModal, setPendingSats]) | ||||
| 
 | ||||
|   const disabled = useMemo(() => item?.mine || item?.meForward || item?.deletedAt, | ||||
|     [item?.mine, item?.meForward, item?.deletedAt]) | ||||
| @ -258,7 +236,11 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats } | ||||
| 
 | ||||
|                 strike() | ||||
| 
 | ||||
|                 setPendingSats(pendingSats + sats) | ||||
|                 setPendingSats(pendingSats => { | ||||
|                   const zapAmount = pendingSats + sats | ||||
|                   zap(zapAmount) | ||||
|                   return zapAmount | ||||
|                 }) | ||||
|               } | ||||
|               : () => showModal(onClose => <ItemAct onClose={onClose} itemId={item.id} act={act} strike={strike} />) | ||||
|           } | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import { useCallback, useEffect, useState } from 'react' | ||||
| import { useCallback, useState } from 'react' | ||||
| import useNoInitialEffect from './use-no-initial-effect' | ||||
| 
 | ||||
| export function debounce (fn, time) { | ||||
|   let timeoutId | ||||
| @ -19,6 +20,6 @@ export function debounce (fn, time) { | ||||
| export default function useDebounceCallback (fn, time, deps = []) { | ||||
|   const [args, setArgs] = useState([]) | ||||
|   const memoFn = useCallback(fn, deps) | ||||
|   useEffect(debounce(() => memoFn(...args), time), [memoFn, time, args, ...deps]) | ||||
|   useNoInitialEffect(debounce(() => memoFn(...args), time), [memoFn, time, args, ...deps]) | ||||
|   return useCallback((...args) => setArgs(args), []) | ||||
| } | ||||
|  | ||||
							
								
								
									
										13
									
								
								components/use-no-initial-effect.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								components/use-no-initial-effect.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| import { useEffect, useRef } from 'react' | ||||
| 
 | ||||
| export default function useNoInitialEffect (func, deps) { | ||||
|   const didMount = useRef(false) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (didMount.current) { | ||||
|       return func() | ||||
|     } else { | ||||
|       didMount.current = true | ||||
|     } | ||||
|   }, deps) | ||||
| } | ||||
| @ -319,7 +319,7 @@ export function LnAddrWithdrawal () { | ||||
| 
 | ||||
|     setAddrOptions(options) | ||||
|     setFormSchema(lnAddrSchema(options)) | ||||
|   }, 500, []) | ||||
|   }, 500, [setAddrOptions, setFormSchema]) | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user