at main 105 lines 2.7 kB view raw
1import { useContext, useEffect, useState } from 'react'; 2 3const loadingDefault = () => ( 4 <em>Loading&hellip;</em> 5); 6 7const errorDefault = err => ( 8 <span className="error"> 9 <strong>Error</strong>:<br/>{`${err}`} 10 </span> 11); 12 13export function Fetch({ using, args, ok, loading, error }) { 14 const [asyncData, setAsyncData] = useState({ state: null }); 15 16 useEffect(() => { 17 let ignore = false; 18 setAsyncData({ state: 'loading' }); 19 (async () => { 20 try { 21 const data = await using(...args); 22 !ignore && setAsyncData({ state: 'done', data }); 23 } catch (err) { 24 !ignore && setAsyncData({ state: 'error', err }); 25 } 26 })(); 27 return () => { ignore = true; } 28 }, args); 29 30 if (asyncData.state === 'loading') { 31 return (loading || loadingDefault)(...args); 32 } else if (asyncData.state === 'error') { 33 return (error || errorDefault)(asyncData.err); 34 } else if (asyncData.state === null) { 35 return <span>wat, request has not started (bug?)</span>; 36 } else { 37 if (asyncData.state !== 'done') { console.warn(`unexpected async data state: ${asyncData.state}`); } 38 return ok(asyncData.data); 39 } 40} 41 42///// 43 44async function getJson(url, credentials) { 45 const opts = {}; 46 if (credentials) opts.credentials = 'include'; 47 const res = await fetch(url, opts); 48 if (!res.ok) { 49 const m = await res.text(); 50 throw new Error(`Failed to fetch: ${m}`); 51 } 52 return await res.json(); 53} 54 55export function GetJson({ endpoint, params, credentials, ...forFetch }) { 56 const host = import.meta.env.VITE_NOTIFICATIONS_HOST; 57 const url = new URL(endpoint, host); 58 for (let [key, val] of Object.entries(params ?? {})) { 59 url.searchParams.append(key, val); 60 } 61 return ( 62 <Fetch 63 using={getJson} 64 args={[url.toString(), credentials]} 65 {...forFetch} 66 /> 67 ); 68} 69 70export async function postJson(url, body, credentials) { 71 const opts = { 72 method: 'POST', 73 headers: {'Content-Type': 'applicaiton/json'}, 74 body, 75 }; 76 if (credentials) opts.credentials = 'include'; 77 const res = await fetch(url, opts); 78 if (!res.ok) { 79 const m = await res.text(); 80 let reason 81 try { 82 reason = JSON.parse(m)?.reason; 83 } catch (err) {}; 84 if (reason) throw reason; 85 throw new Error(`Failed to fetch: ${m}`); 86 } 87 try { 88 return await res.json(); 89 } catch (e) { 90 if ([201, 204].includes(res.status)) return null; 91 throw e; 92 } 93} 94 95export function PostJson({ endpoint, data, credentials, ...forFetch }) { 96 const host = import.meta.env.VITE_NOTIFICATIONS_HOST; 97 const url = new URL(endpoint, host); 98 return ( 99 <Fetch 100 using={postJson} 101 args={[url.toString(), JSON.stringify(data), credentials]} 102 {...forFetch} 103 /> 104 ); 105}