Created
June 17, 2021 19:50
-
-
Save bengotow/e08f4a2155ecdac2a629e7ca95ba9de3 to your computer and use it in GitHub Desktop.
useResource with Examples
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Basic usage to fetch data for display | |
export const NotesTabContent: React.FunctionComponent<{ | |
scope: CoreAPI.TabScope; | |
boundsForExports: TimeBounds<number>; | |
}> = ({ scope, boundsForExports }) => { | |
const [kinds, setKinds] = useQueryPersistedState<string[]>({ | |
encode: kinds => ({ kinds: kinds.join(',') }), | |
decode: qs => (qs.kinds ? qs.kinds.split(',') : ['New', 'Known']), | |
}); | |
const [notes] = useResource<PeopleAPI.ProfileNoteResponse[]>(`/talent/api/notes`, { | |
...scope, | |
kinds, | |
count: 100, | |
}); | |
return ( | |
<div style={{ display: 'flex' }}> | |
<Column style={{ width: '65%' }}> | |
{notes && notes.length > 0 ? ( | |
<List | |
pagination={{ size: 'small' }} | |
dataSource={notes} | |
renderItem={note => <NoteRow key={note.id} note={note} />} | |
/> | |
) : ( | |
<SpinnerOrEmptyState data={notes} title="No recent notes" /> | |
)} | |
</Column> | |
</div> | |
) | |
} | |
// Usage with the second parameter, which provides pre-built refresh / PUT / POST APIs following a restful schema, and updates the cached result value. | |
export const PageConfig = ({match}) => { | |
const [configs = [], { putItem }] = useResource<CoreAPI.Config[], CoreAPI.Config>( | |
'/talent/api/config' | |
); | |
const config = configs.find(c => c.key === match.params.key); | |
return ( | |
<Editor | |
config={config} | |
onSave={async config => { | |
await putItem(config); | |
return config; | |
}} | |
key={config.key} | |
/> | |
); | |
}; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { message } from 'antd'; | |
import qs from 'query-string'; | |
import React from 'react'; | |
// This assumes the presence of a function called `makeRequest` that runs the actual fetch calls to the backend (probably with your auth headers, etc) | |
interface ResourceConfig { | |
silent?: boolean; | |
GETRequestMethod?: 'GET' | 'POST'; | |
} | |
export interface ResourceOps<T, U = Partial<T>> { | |
post: (v: U) => Promise<void>; | |
put: (v: U) => Promise<void>; | |
putItem: (item: { id: string | number } & U) => Promise<void>; | |
delete: () => Promise<void>; | |
deleteItem: (item: string | number | { id: string | number }) => Promise<void>; | |
applyLocalUpdates: (v: T) => void; | |
refresh: () => Promise<T>; | |
} | |
export function useResource<T, U = Partial<T>>( | |
path: string, | |
query?: { [key: string]: any }, | |
config?: ResourceConfig | |
) { | |
const GETRequestMethod = config?.GETRequestMethod || 'GET'; | |
const [state, setState] = React.useState<{ value: T; url: string } | undefined>(undefined); | |
const url = `${path}${path.includes('?') ? '&' : '?'}${ | |
GETRequestMethod === 'GET' && query ? qs.stringify(query) : '' | |
}`; | |
React.useEffect(() => { | |
const fetch = async () => { | |
setState(undefined); | |
setState({ | |
url, | |
value: await makeRequest<T>( | |
url, | |
GETRequestMethod, | |
GETRequestMethod === 'POST' ? query : undefined | |
), | |
}); | |
}; | |
void fetch(); | |
}, [url]); | |
const ops: ResourceOps<T, U> = { | |
post: async (v: U) => { | |
try { | |
const resp = await makeRequest<U>(path, 'POST', v); | |
if ('id' in resp && state && state.value instanceof Array) { | |
setState({ ...state, value: [...state.value, resp] as any }); | |
} else { | |
console.warn( | |
'useResource: POST state update skipped, response contains no id or state.value is not an array' | |
); | |
} | |
!config?.silent && void message.success('Item created'); | |
} catch (err) { | |
!config?.silent && void message.error('Failed to save, try again.'); | |
throw err; | |
} | |
}, | |
put: async (v: U) => { | |
try { | |
const resp = await makeRequest<T>(path, 'PUT', v); | |
if (state) { | |
if ('id' in resp) { | |
setState({ ...state, value: Object.assign({}, state.value, resp) }); | |
} else if (state.value instanceof Array && resp instanceof Array) { | |
setState({ ...state, value: resp }); | |
} else { | |
console.warn( | |
'useResource: PUT state update skipped, response does not look like object or array item' | |
); | |
} | |
} | |
!config?.silent && void message.success('Changes saved'); | |
} catch (err) { | |
!config?.silent && void message.error('Failed to save, try again.'); | |
throw err; | |
} | |
}, | |
putItem: async (item: { id: string | number } & U) => { | |
const resp = await makeRequest<any>(`${path}/${item.id}`, 'PUT', item); | |
if (resp && 'id' in resp && state && state.value instanceof Array) { | |
const nextValue: any = []; | |
for (const item of state.value) { | |
nextValue.push(item.id === resp.id ? resp : item); | |
} | |
setState({ ...state, value: nextValue }); | |
} else { | |
console.warn( | |
'useResource: PUT state update skipped, response does not look like array item' | |
); | |
} | |
!config?.silent && void message.success('Updated successfully'); | |
}, | |
delete: async () => { | |
await makeRequest<T>(path, 'DELETE'); | |
!config?.silent && void message.success('Deleted successfully'); | |
}, | |
deleteItem: async (item: string | number | { id: string | number }) => { | |
const itemId = typeof item === 'object' && 'id' in item ? item.id : item; | |
await makeRequest<T>(`${path}/${itemId}`, 'DELETE'); | |
!config?.silent && void message.success('Deleted successfully'); | |
if (state && state.value instanceof Array) { | |
setState({ ...state, value: state.value.filter(i => i.id !== itemId) as any }); | |
} else { | |
console.warn( | |
'useResource: DELETE state update skipped, local state is not an array of items' | |
); | |
} | |
}, | |
applyLocalUpdates: (v: T) => { | |
setState({ url, value: v }); | |
}, | |
refresh: async () => { | |
const v = await makeRequest<T>( | |
url, | |
GETRequestMethod, | |
GETRequestMethod === 'POST' ? query : undefined | |
); | |
setState({ url, value: v }); | |
return v; | |
}, | |
}; | |
// Explicitly tell TS this is a tuple of two distinct types, not an array of (T | typeof Ops) | |
if (state?.url === url) { | |
return [state.value, ops] as [T | undefined, ResourceOps<T, U>]; | |
} else { | |
return [undefined, ops] as [T | undefined, ResourceOps<T, U>]; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment