Converted put(api/announcement) to use useSend

Added useSendButtonCaption hook
Related to #19
This commit is contained in:
Dmitriy Shishkov 2023-07-17 12:18:54 +03:00
parent b360b06d34
commit b7bbd937b4
Signed by: dm1sh
GPG Key ID: 027994B0AA357688
6 changed files with 100 additions and 95 deletions

View File

@ -0,0 +1,12 @@
import { API_URL } from '../../config'
import { PutAnnouncement, PutAnnouncementResponse } from './types'
const composePutAnnouncementURL = () => (
API_URL + '/announcement?'
)
const processPutAnnouncement = (data: PutAnnouncementResponse): PutAnnouncement => {
return data.Answer
}
export { composePutAnnouncementURL, processPutAnnouncement }

View File

@ -0,0 +1,17 @@
import { isObject } from '../../utils/types'
type PutAnnouncementResponse = {
Answer: boolean
}
const isPutAnnouncementResponse = (obj: unknown): obj is PutAnnouncementResponse => (
isObject(obj, {
'Answer': 'boolean'
})
)
type PutAnnouncement = boolean
export type { PutAnnouncementResponse, PutAnnouncement }
export { isPutAnnouncementResponse }

View File

@ -1,79 +1,31 @@
import { useEffect, useRef, useState } from 'react'
import { useCallback } from 'react'
import { API_URL } from '../../config'
import { isLiteralUnion } from '../../utils/types'
import { handleHTTPErrors } from '../../utils'
const addErrors = ['Не удалось опубликовать объявление', 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const
type AddError = typeof addErrors[number]
const isAddError = (obj: unknown): obj is AddError => (
isLiteralUnion(obj, addErrors)
)
const buttonStates = ['Опубликовать', 'Загрузка...', 'Опубликовано', 'Отменено'] as const
type ButtonState = typeof buttonStates[number] | AddError
type AddResponse = {
Answer: boolean
}
const isAddResponse = (obj: unknown): obj is AddResponse => (
typeof obj === 'object' && obj !== null && typeof Reflect.get(obj, 'Answer') === 'boolean'
)
import { useSend } from '..'
import { composePutAnnouncementURL, processPutAnnouncement } from '../../api/putAnnouncement'
import { isPutAnnouncementResponse } from '../../api/putAnnouncement/types'
import useSendButtonCaption from '../useSendButtonCaption'
const useAddAnnouncement = () => {
const [status, setStatus] = useState<ButtonState>('Опубликовать')
const { doSend, loading, error } = useSend(
composePutAnnouncementURL(),
'PUT',
true,
isPutAnnouncementResponse,
processPutAnnouncement,
)
const timerIdRef = useRef<number>()
const abortControllerRef = useRef<AbortController>()
const { update, ...button } = useSendButtonCaption('Опубликовать', loading, error, 'Опубликовано')
const doAdd = async (formData: FormData) => {
if (status === 'Загрузка...') {
abortControllerRef.current?.abort()
setStatus('Отменено')
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 3000)
return
}
const doSendWithButton = useCallback(async (formData: FormData) => {
const data = await doSend({}, {
body: formData
})
update(data)
setStatus('Загрузка...')
return data
}, [doSend, update])
const abortController = new AbortController()
abortControllerRef.current = abortController
try {
const res = await fetch(API_URL + '/announcement', {
method: 'PUT',
body: formData,
signal: abortController.signal
})
handleHTTPErrors(res)
const data: unknown = await res.json()
if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера')
if (!data.Answer) {
throw new Error('Не удалось опубликовать объявление')
}
setStatus('Опубликовано')
} catch (err) {
setStatus(isAddError(err) ? err : 'Неизвестная ошибка')
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 10000)
}
}
useEffect(() => {
const abortController = abortControllerRef.current
return () => {
clearTimeout(timerIdRef.current)
abortController?.abort()
}
})
return { doAdd, status }
return { doSend: doSendWithButton, button }
}
export default useAddAnnouncement

View File

@ -24,6 +24,7 @@ function useSend<R, T>(
/** Don't use in useEffect. If you need request result, go with useFetch instead */
const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => {
setLoading(true)
setError(null)
if (abortControllerRef.current) {
abortControllerRef.current.abort()

View File

@ -0,0 +1,44 @@
import { useCallback, useEffect, useState } from 'react'
function useSendButtonCaption(
initial: string,
loading: boolean,
error: string | null,
result = initial,
singular = true
) {
const [caption, setCaption] = useState(initial)
const [disabled, setDisabled] = useState(false)
const [title, setTitle] = useState(initial)
const update = useCallback(<T extends NonNullable<unknown>>(data: T | undefined) => {
if (data !== undefined) {
setCaption(result)
setTitle('Отправить ещё раз')
if (singular) {
setDisabled(true)
setTitle('')
}
}
}, [result, singular])
useEffect(() => {
if (loading) {
setCaption('Загрузка...')
setTitle('Отменить и отправить ещё раз')
}
}, [loading])
useEffect(() => {
if (!loading && error !== null) {
setCaption(error + ', нажмите, чтобы попробовать ещё раз')
setTitle('')
}
}, [error, loading])
return { update, children: caption, disabled, title }
}
export default useSendButtonCaption

View File

@ -1,11 +1,10 @@
import { CSSProperties, FormEventHandler, useEffect, useState } from 'react'
import { CSSProperties, FormEventHandler, useState } from 'react'
import { Form, Button, Card } from 'react-bootstrap'
import { MapContainer, TileLayer } from 'react-leaflet'
import { latLng } from 'leaflet'
import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components'
import { useAddAnnouncement, useTrashboxes } from '../hooks/api'
import { handleHTTPErrors } from '../utils'
import { categories, categoryNames } from '../assets/category'
import { stations, lines, lineNames } from '../assets/metro'
import { fallbackError, gotError } from '../hooks/useFetch'
@ -32,25 +31,7 @@ function AddPage() {
const address = useOsmAddresses(addressPosition)
useEffect(() => {
if (!gotError(address))
void (async () => {
try {
const res = await fetch(location.protocol + '//nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(address.data))
handleHTTPErrors(res)
const fetchData: unknown = await res.json()
console.log('f', fetchData)
} catch (err) {
console.error(err)
}
})()
}, [address])
const { doAdd, status } = useAddAnnouncement()
const { doSend, button } = useAddAnnouncement()
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault()
@ -63,7 +44,7 @@ function AddPage() {
formData.append('address', address.data || '') // if address.error
formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString())
void doAdd(formData)
void doSend(formData)
}
return (
@ -151,7 +132,7 @@ function AddPage() {
</Form.Select>
</Form.Group>
<Form.Group className='mb-3' controlId='password'>
<Form.Group className='mb-3' controlId='trashbox'>
<Form.Label>Пункт сбора мусора</Form.Label>
<div className='mb-3'>
{trashboxes.loading
@ -195,9 +176,7 @@ function AddPage() {
)}
</Form.Group>
<Button variant='success' type='submit'>
{status}
</Button>
<Button variant='success' type='submit' {...button} />
</Form>
</Card.Body>
</Card>