| 1 |  |  | import { | 
            
                                                        
            
                                    
            
            
                | 2 |  |  |   Reducer, | 
            
                                                        
            
                                    
            
            
                | 3 |  |  |   useCallback, | 
            
                                                        
            
                                    
            
            
                | 4 |  |  |   useEffect, | 
            
                                                        
            
                                    
            
            
                | 5 |  |  |   useMemo, | 
            
                                                        
            
                                    
            
            
                | 6 |  |  |   useReducer, | 
            
                                                        
            
                                    
            
            
                | 7 |  |  |   useRef, | 
            
                                                        
            
                                    
            
            
                | 8 |  |  | } from "react"; | 
            
                                                        
            
                                    
            
            
                | 9 |  |  | import { | 
            
                                                        
            
                                    
            
            
                | 10 |  |  |   deleteRequest, | 
            
                                                        
            
                                    
            
            
                | 11 |  |  |   FetchError, | 
            
                                                        
            
                                    
            
            
                | 12 |  |  |   getRequest, | 
            
                                                        
            
                                    
            
            
                | 13 |  |  |   postRequest, | 
            
                                                        
            
                                    
            
            
                | 14 |  |  |   processJsonResponse, | 
            
                                                        
            
                                    
            
            
                | 15 |  |  |   putRequest, | 
            
                                                        
            
                                    
            
            
                | 16 |  |  | } from "../../helpers/httpRequests"; | 
            
                                                        
            
                                    
            
            
                | 17 |  |  | import { getId, hasKey, identity } from "../../helpers/queries"; | 
            
                                                        
            
                                    
            
            
                | 18 |  |  | import indexCrudReducer, { | 
            
                                                        
            
                                    
            
            
                | 19 |  |  |   initializeState, | 
            
                                                        
            
                                    
            
            
                | 20 |  |  |   ResourceState, | 
            
                                                        
            
                                    
            
            
                | 21 |  |  |   ActionTypes, | 
            
                                                        
            
                                    
            
            
                | 22 |  |  |   AsyncAction, | 
            
                                                        
            
                                    
            
            
                | 23 |  |  | } from "./indexCrudReducer"; | 
            
                                                        
            
                                    
            
            
                | 24 |  |  | import { Json, ResourceStatus } from "./types"; | 
            
                                                        
            
                                    
            
            
                | 25 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 26 |  |  | type IndexedObject<T> = | 
            
                                                        
            
                                    
            
            
                | 27 |  |  |   | { | 
            
                                                        
            
                                    
            
            
                | 28 |  |  |       [key: string]: T; | 
            
                                                        
            
                                    
            
            
                | 29 |  |  |     } | 
            
                                                        
            
                                    
            
            
                | 30 |  |  |   | { [key: number]: T }; | 
            
                                                        
            
                                    
            
            
                | 31 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 32 |  |  | function valuesSelector<T>(state: ResourceState<T>): IndexedObject<T> { | 
            
                                                        
            
                                    
            
            
                | 33 |  |  |   return Object.entries(state.values).reduce( | 
            
                                                        
            
                                    
            
            
                | 34 |  |  |     (collection: IndexedObject<T>, [key, item]) => { | 
            
                                                        
            
                                    
            
            
                | 35 |  |  |       collection[key] = item.value; | 
            
                                                        
            
                                    
            
            
                | 36 |  |  |       return collection; | 
            
                                                        
            
                                    
            
            
                | 37 |  |  |     }, | 
            
                                                        
            
                                    
            
            
                | 38 |  |  |     {}, | 
            
                                                        
            
                                    
            
            
                | 39 |  |  |   ); | 
            
                                                        
            
                                    
            
            
                | 40 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 41 |  |  | function statusSelector<T>( | 
            
                                                        
            
                                    
            
            
                | 42 |  |  |   state: ResourceState<T>, | 
            
                                                        
            
                                    
            
            
                | 43 |  |  | ): IndexedObject<ResourceStatus> { | 
            
                                                        
            
                                    
            
            
                | 44 |  |  |   // If the entire index is being refreshed, then each individual item should be considered "pending". | 
            
                                                        
            
                                    
            
            
                | 45 |  |  |   const forcePending = state.indexMeta.status === "pending"; | 
            
                                                        
            
                                    
            
            
                | 46 |  |  |   return Object.entries(state.values).reduce( | 
            
                                                        
            
                                    
            
            
                | 47 |  |  |     (collection: IndexedObject<ResourceStatus>, [key, item]) => { | 
            
                                                        
            
                                    
            
            
                | 48 |  |  |       collection[key] = forcePending ? "pending" : item.status; | 
            
                                                        
            
                                    
            
            
                | 49 |  |  |       return collection; | 
            
                                                        
            
                                    
            
            
                | 50 |  |  |     }, | 
            
                                                        
            
                                    
            
            
                | 51 |  |  |     {}, | 
            
                                                        
            
                                    
            
            
                | 52 |  |  |   ); | 
            
                                                        
            
                                    
            
            
                | 53 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 54 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 55 |  |  | // Defining these functions outside of the hook, despite their simplicity, | 
            
                                                        
            
                                    
            
            
                | 56 |  |  | // so they remain constant between re-renders. | 
            
                                                        
            
                                    
            
            
                | 57 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 58 |  |  | function defaultEntityEndpoint(baseEndpoint: string, entity: any): string { | 
            
                                                        
            
                                    
            
            
                | 59 |  |  |   if (!hasKey(entity, "id")) { | 
            
                                                        
            
                                    
            
            
                | 60 |  |  |     throw new Error( | 
            
                                                        
            
                                    
            
            
                | 61 |  |  |       'Cannot use default resolveEntityEndpoint on item without an "id" property', | 
            
                                                        
            
                                    
            
            
                | 62 |  |  |     ); | 
            
                                                        
            
                                    
            
            
                | 63 |  |  |   } | 
            
                                                        
            
                                    
            
            
                | 64 |  |  |   return `${baseEndpoint}/${entity.id}`; | 
            
                                                        
            
                                    
            
            
                | 65 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 66 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 67 |  |  | function defaultCreateEndpoint(baseEndpoint: string): string { | 
            
                                                        
            
                                    
            
            
                | 68 |  |  |   return baseEndpoint; | 
            
                                                        
            
                                    
            
            
                | 69 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 70 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 71 |  |  | function defaultKeyFn(item: any): number { | 
            
                                                        
            
                                    
            
            
                | 72 |  |  |   if (!hasKey(item, "id")) { | 
            
                                                        
            
                                    
            
            
                | 73 |  |  |     throw new Error( | 
            
                                                        
            
                                    
            
            
                | 74 |  |  |       'Cannot use default keyFn on item without an "id" property', | 
            
                                                        
            
                                    
            
            
                | 75 |  |  |     ); | 
            
                                                        
            
                                    
            
            
                | 76 |  |  |   } | 
            
                                                        
            
                                    
            
            
                | 77 |  |  |   return getId(item); | 
            
                                                        
            
                                    
            
            
                | 78 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 79 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 80 |  |  | function doNothing(): void { | 
            
                                                        
            
                                    
            
            
                | 81 |  |  |   /* do nothing */ | 
            
                                                        
            
                                    
            
            
                | 82 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 83 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 84 |  |  | // The value dispatched to the reducer must have an id, or the reducer cannot place it correctly. | 
            
                                                        
            
                                    
            
            
                | 85 |  |  | function isValidEntity(value: any): boolean { | 
            
                                                        
            
                                    
            
            
                | 86 |  |  |   return hasKey(value, "id"); | 
            
                                                        
            
                                    
            
            
                | 87 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 88 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 89 |  |  | function isValidEntityList(value: any): boolean { | 
            
                                                        
            
                                    
            
            
                | 90 |  |  |   return Array.isArray(value) && value.every(isValidEntity); | 
            
                                                        
            
                                    
            
            
                | 91 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 92 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 93 |  |  | export const UNEXPECTED_FORMAT_ERROR = | 
            
                                                        
            
                                    
            
            
                | 94 |  |  |   "Response from server was not expected format"; | 
            
                                                        
            
                                    
            
            
                | 95 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 96 |  |  | /** | 
            
                                                        
            
                                    
            
            
                | 97 |  |  |  * This hook keeps a local list of entities in sync with a REST api representing a single resource. | 
            
                                                        
            
                                    
            
            
                | 98 |  |  |  * | 
            
                                                        
            
                                    
            
            
                | 99 |  |  |  * *** Interaction with API *** | 
            
                                                        
            
                                    
            
            
                | 100 |  |  |  * The API can be interacted with in 4 ways: | 
            
                                                        
            
                                    
            
            
                | 101 |  |  |  *   - Refreshing, getting a complete list of entities | 
            
                                                        
            
                                    
            
            
                | 102 |  |  |  *   - Creating a new entity that is added to the list | 
            
                                                        
            
                                    
            
            
                | 103 |  |  |  *   - Updating a specific entity in the list | 
            
                                                        
            
                                    
            
            
                | 104 |  |  |  *   - Deleting a specific entity in the list | 
            
                                                        
            
                                    
            
            
                | 105 |  |  |  * | 
            
                                                        
            
                                    
            
            
                | 106 |  |  |  * The only required argument is the API endpoint. By default, the CRUD operations (Create, Read, Update, Delete) follow normal REST conventions: | 
            
                                                        
            
                                    
            
            
                | 107 |  |  |  *   - Create submits a POST request to endpoint | 
            
                                                        
            
                                    
            
            
                | 108 |  |  |  *   - Refresh submits a GET request to endpoint | 
            
                                                        
            
                                    
            
            
                | 109 |  |  |  *   - Update submits a PUT request to endpoint/id | 
            
                                                        
            
                                    
            
            
                | 110 |  |  |  *   - Delete submits a DELETE request to endpoint/id | 
            
                                                        
            
                                    
            
            
                | 111 |  |  |  * The urls used may be modified by overriding resolveEntityEndpoint (for update and delete requests) and resolveCreateEndpoint. | 
            
                                                        
            
                                    
            
            
                | 112 |  |  |  * This may allow, for example, for using query parameters in some of these urls. | 
            
                                                        
            
                                    
            
            
                | 113 |  |  |  * Note: The HTTP verbs used cannot be changed. | 
            
                                                        
            
                                    
            
            
                | 114 |  |  |  * | 
            
                                                        
            
                                    
            
            
                | 115 |  |  |  * The api requests MUST return valid JSON (except for delete requests), or the request will be considered to have failed. | 
            
                                                        
            
                                    
            
            
                | 116 |  |  |  * The response to refresh requests must be a JSON List, and update and create requests must return the resulting entity as a json object. | 
            
                                                        
            
                                    
            
            
                | 117 |  |  |  * However, the JSON objects may be preprocessed before being stored locally, by overriding parseEntityResponse and/or parseIndexResponse. | 
            
                                                        
            
                                    
            
            
                | 118 |  |  |  * | 
            
                                                        
            
                                    
            
            
                | 119 |  |  |  * *** Hook Values and Statuses *** * | 
            
                                                        
            
                                    
            
            
                | 120 |  |  |  * values: This represents the list of entities compressing the resource. | 
            
                                                        
            
                                    
            
            
                | 121 |  |  |  *   Note: values is not an array. It is an object with each entity indexed by id, so specific items may be retrieved more easily. | 
            
                                                        
            
                                    
            
            
                | 122 |  |  |  *   Note: By default, values starts out empty and the hook immediately triggers a refresh callback. | 
            
                                                        
            
                                    
            
            
                | 123 |  |  |  *   Note: An initialValue may be provided, which suppresses that initial refresh, unless forceInitialRefresh is ALSO overridden with true. | 
            
                                                        
            
                                    
            
            
                | 124 |  |  |  *   Note: Setting forceInitialRefresh to false has no effect. Set initialValue to [] instead. | 
            
                                                        
            
                                    
            
            
                | 125 |  |  |  * | 
            
                                                        
            
                                    
            
            
                | 126 |  |  |  * indexStatus, createStatus, and entityStatus: These tell you whether any requests are currently in progress, and if not whether the last completed request was successful. | 
            
                                                        
            
                                    
            
            
                | 127 |  |  |  *   Note: entityStatus covers both update and delete requests, and contains a status value for each entity, indexed by id. | 
            
                                                        
            
                                    
            
            
                | 128 |  |  |  * | 
            
                                                        
            
                                    
            
            
                | 129 |  |  |  * *** Callbacks *** * | 
            
                                                        
            
                                    
            
            
                | 130 |  |  |  * create, refresh, update, deleteResource: These callbacks trigger requests to the api, updating values and the status properties accordingly. | 
            
                                                        
            
                                    
            
            
                | 131 |  |  |  *   Note: while conventional "reactive" programming would control UI based only on the current values and status properties, these callbacks also return promises | 
            
                                                        
            
                                    
            
            
                | 132 |  |  |  *         which allow code to respond to the success or failure of specific requests. | 
            
                                                        
            
                                    
            
            
                | 133 |  |  |  * | 
            
                                                        
            
                                    
            
            
                | 134 |  |  |  * *** Error Handling *** | 
            
                                                        
            
                                    
            
            
                | 135 |  |  |  * You may watch for statuses of "rejected" to determining whether an error occurred during certain requests. | 
            
                                                        
            
                                    
            
            
                | 136 |  |  |  * To respond to any details of potential errors, override handleError. | 
            
                                                        
            
                                    
            
            
                | 137 |  |  |  * Note: If the error was caused by a non-200 response from the server, handleError will receive an instance of FetchError, which contains the entire response object. | 
            
                                                        
            
                                    
            
            
                | 138 |  |  |  */ | 
            
                                                        
            
                                    
            
            
                | 139 |  |  | export function useResourceIndex<T>( | 
            
                                                        
            
                                    
            
            
                | 140 |  |  |   endpoint: string, // API endpoint that returns a list of T. | 
            
                                                        
            
                                    
            
            
                | 141 |  |  |   overrides?: { | 
            
                                                        
            
                                    
            
            
                | 142 |  |  |     initialValue?: T[]; // Defaults to an empty list. If this is overridden, initial fetch is skipped (unless forceInitialRefresh is set to true). | 
            
                                                        
            
                                    
            
            
                | 143 |  |  |     forceInitialRefresh?: boolean; // If you set an initialValue but also want to refresh immediately, set this to true. | 
            
                                                        
            
                                    
            
            
                | 144 |  |  |     parseEntityResponse?: (response: Json) => T; // Defaults to the identity function. | 
            
                                                        
            
                                    
            
            
                | 145 |  |  |     parseIndexResponse?: (response: Json) => T[]; // Defaults to (response) => response.map(parseEntityResponse) | 
            
                                                        
            
                                    
            
            
                | 146 |  |  |     resolveEntityEndpoint?: (baseEndpoint: string, entity: T) => string; // Defaults to appending '/id' to baseEndpoint. Used for update (PUT) and delete (DELETE) requests. | 
            
                                                        
            
                                    
            
            
                | 147 |  |  |     resolveCreateEndpoint?: (baseEndpoint: string, newEntity: T) => string; // Defaults to identical to endpoint. Used for create (POST) requests. | 
            
                                                        
            
                                    
            
            
                | 148 |  |  |     handleError?: (error: Error | FetchError) => void; | 
            
                                                        
            
                                    
            
            
                | 149 |  |  |     keyFn?: (item: T) => string | number; // Returns a unique key for each item. Defaults to using `item.id`. | 
            
                                                        
            
                                    
            
            
                | 150 |  |  |   }, | 
            
                                                        
            
                                    
            
            
                | 151 |  |  | ): { | 
            
                                                        
            
                                    
            
            
                | 152 |  |  |   values: IndexedObject<T>; | 
            
                                                        
            
                                    
            
            
                | 153 |  |  |   indexStatus: ResourceStatus; // The state of any requests to reload the entire index. | 
            
                                                        
            
                                    
            
            
                | 154 |  |  |   createStatus: ResourceStatus; // If ANY create requests are in progress, this is 'pending'. Otherwise, it is 'fulfilled' or 'rejected' depending on the last request to complete. | 
            
                                                        
            
                                    
            
            
                | 155 |  |  |   entityStatus: IndexedObject<ResourceStatus>; // Note that if indexStatus is 'pending', every entity status will also be 'pending'. | 
            
                                                        
            
                                    
            
            
                | 156 |  |  |   create: (newValue: T) => Promise<T>; | 
            
                                                        
            
                                    
            
            
                | 157 |  |  |   refresh: () => Promise<T[]>; // Reloads the entire index. | 
            
                                                        
            
                                    
            
            
                | 158 |  |  |   update: (newValue: T) => Promise<T>; | 
            
                                                        
            
                                    
            
            
                | 159 |  |  |   deleteResource: (value: T) => Promise<void>; | 
            
                                                        
            
                                    
            
            
                | 160 |  |  | } { | 
            
                                                        
            
                                    
            
            
                | 161 |  |  |   const initialValue = overrides?.initialValue ?? []; | 
            
                                                        
            
                                    
            
            
                | 162 |  |  |   const doInitialRefresh = | 
            
                                                        
            
                                    
            
            
                | 163 |  |  |     overrides?.initialValue === undefined || | 
            
                                                        
            
                                    
            
            
                | 164 |  |  |     overrides?.forceInitialRefresh === true; | 
            
                                                        
            
                                    
            
            
                | 165 |  |  |   const parseEntityResponse = overrides?.parseEntityResponse ?? identity; | 
            
                                                        
            
                                    
            
            
                | 166 |  |  |   const parseIndexResponse = useMemo( | 
            
                                                        
            
                                    
            
            
                | 167 |  |  |     () => | 
            
                                                        
            
                                    
            
            
                | 168 |  |  |       overrides?.parseIndexResponse ?? | 
            
                                                        
            
                                    
            
            
                | 169 |  |  |       ((response: Json): T[] => response.map(parseEntityResponse)), | 
            
                                                        
            
                                    
            
            
                | 170 |  |  |     [overrides?.parseIndexResponse, parseEntityResponse], | 
            
                                                        
            
                                    
            
            
                | 171 |  |  |   ); | 
            
                                                        
            
                                    
            
            
                | 172 |  |  |   const resolveEntityEndpoint = | 
            
                                                        
            
                                    
            
            
                | 173 |  |  |     overrides?.resolveEntityEndpoint ?? defaultEntityEndpoint; | 
            
                                                        
            
                                    
            
            
                | 174 |  |  |   const resolveCreateEndpoint = | 
            
                                                        
            
                                    
            
            
                | 175 |  |  |     overrides?.resolveCreateEndpoint ?? defaultCreateEndpoint; | 
            
                                                        
            
                                    
            
            
                | 176 |  |  |   const handleError = overrides?.handleError ?? doNothing; | 
            
                                                        
            
                                    
            
            
                | 177 |  |  |   const keyFn = overrides?.keyFn ?? defaultKeyFn; | 
            
                                                        
            
                                    
            
            
                | 178 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 179 |  |  |   const addKey = useCallback( | 
            
                                                        
            
                                    
            
            
                | 180 |  |  |     (item: T): { item: T; key: string | number } => { | 
            
                                                        
            
                                    
            
            
                | 181 |  |  |       return { item, key: keyFn(item) }; | 
            
                                                        
            
                                    
            
            
                | 182 |  |  |     }, | 
            
                                                        
            
                                    
            
            
                | 183 |  |  |     [keyFn], | 
            
                                                        
            
                                    
            
            
                | 184 |  |  |   ); | 
            
                                                        
            
                                    
            
            
                | 185 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 186 |  |  |   const isSubscribed = useRef(true); | 
            
                                                        
            
                                    
            
            
                | 187 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 188 |  |  |   const [state, dispatch] = useReducer< | 
            
                                                        
            
                                    
            
            
                | 189 |  |  |     Reducer<ResourceState<T>, AsyncAction<T>>, | 
            
                                                        
            
                                    
            
            
                | 190 |  |  |     { item: T; key: string | number }[] // This represent type of initialValue, passed to initializeState to create initial state. | 
            
                                                        
            
                                    
            
            
                | 191 |  |  |   >(indexCrudReducer, initialValue.map(addKey), initializeState); | 
            
                                                        
            
                                    
            
            
                | 192 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 193 |  |  |   const values = useMemo(() => valuesSelector(state), [state]); | 
            
                                                        
            
                                    
            
            
                | 194 |  |  |   const indexStatus = state.indexMeta.status; | 
            
                                                        
            
                                    
            
            
                | 195 |  |  |   const createStatus = state.createMeta.status; | 
            
                                                        
            
                                    
            
            
                | 196 |  |  |   const entityStatus = useMemo(() => statusSelector(state), [state]); | 
            
                                                        
            
                                    
            
            
                | 197 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 198 |  |  |   const create = useCallback( | 
            
                                                        
            
                                    
            
            
                | 199 |  |  |     async (newValue: T): Promise<T> => { | 
            
                                                        
            
                                    
            
            
                | 200 |  |  |       dispatch({ | 
            
                                                        
            
                                    
            
            
                | 201 |  |  |         type: ActionTypes.CreateStart, | 
            
                                                        
            
                                    
            
            
                | 202 |  |  |         meta: { item: newValue }, | 
            
                                                        
            
                                    
            
            
                | 203 |  |  |       }); | 
            
                                                        
            
                                    
            
            
                | 204 |  |  |       let entity: T; | 
            
                                                        
            
                                    
            
            
                | 205 |  |  |       try { | 
            
                                                        
            
                                    
            
            
                | 206 |  |  |         const json = await postRequest( | 
            
                                                        
            
                                    
            
            
                | 207 |  |  |           resolveCreateEndpoint(endpoint, newValue), | 
            
                                                        
            
                                    
            
            
                | 208 |  |  |           newValue, | 
            
                                                        
            
                                    
            
            
                | 209 |  |  |         ).then(processJsonResponse); | 
            
                                                        
            
                                    
            
            
                | 210 |  |  |         entity = parseEntityResponse(json); | 
            
                                                        
            
                                    
            
            
                | 211 |  |  |         if (!isValidEntity(entity)) { | 
            
                                                        
            
                                    
            
            
                | 212 |  |  |           throw new Error(UNEXPECTED_FORMAT_ERROR); | 
            
                                                        
            
                                    
            
            
                | 213 |  |  |         } | 
            
                                                        
            
                                    
            
            
                | 214 |  |  |       } catch (error) { | 
            
                                                        
            
                                    
            
            
                | 215 |  |  |         if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 216 |  |  |           dispatch({ | 
            
                                                        
            
                                    
            
            
                | 217 |  |  |             type: ActionTypes.CreateReject, | 
            
                                                        
            
                                    
            
            
                | 218 |  |  |             payload: error, | 
            
                                                        
            
                                    
            
            
                | 219 |  |  |             meta: { item: newValue }, | 
            
                                                        
            
                                    
            
            
                | 220 |  |  |           }); | 
            
                                                        
            
                                    
            
            
                | 221 |  |  |           handleError(error); | 
            
                                                        
            
                                    
            
            
                | 222 |  |  |         } | 
            
                                                        
            
                                    
            
            
                | 223 |  |  |         throw error; | 
            
                                                        
            
                                    
            
            
                | 224 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 225 |  |  |       if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 226 |  |  |         dispatch({ | 
            
                                                        
            
                                    
            
            
                | 227 |  |  |           type: ActionTypes.CreateFulfill, | 
            
                                                        
            
                                    
            
            
                | 228 |  |  |           payload: addKey(entity), | 
            
                                                        
            
                                    
            
            
                | 229 |  |  |           meta: { item: newValue }, | 
            
                                                        
            
                                    
            
            
                | 230 |  |  |         }); | 
            
                                                        
            
                                    
            
            
                | 231 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 232 |  |  |       return entity; | 
            
                                                        
            
                                    
            
            
                | 233 |  |  |     }, | 
            
                                                        
            
                                    
            
            
                | 234 |  |  |     [endpoint, resolveCreateEndpoint, parseEntityResponse, handleError, addKey], | 
            
                                                        
            
                                    
            
            
                | 235 |  |  |   ); | 
            
                                                        
            
                                    
            
            
                | 236 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 237 |  |  |   const refresh = useCallback(async (): Promise<T[]> => { | 
            
                                                        
            
                                    
            
            
                | 238 |  |  |     dispatch({ | 
            
                                                        
            
                                    
            
            
                | 239 |  |  |       type: ActionTypes.IndexStart, | 
            
                                                        
            
                                    
            
            
                | 240 |  |  |     }); | 
            
                                                        
            
                                    
            
            
                | 241 |  |  |     let index: T[]; | 
            
                                                        
            
                                    
            
            
                | 242 |  |  |     try { | 
            
                                                        
            
                                    
            
            
                | 243 |  |  |       const json = await getRequest(endpoint).then(processJsonResponse); | 
            
                                                        
            
                                    
            
            
                | 244 |  |  |       index = parseIndexResponse(json); | 
            
                                                        
            
                                    
            
            
                | 245 |  |  |       if (!isValidEntityList(index)) { | 
            
                                                        
            
                                    
            
            
                | 246 |  |  |         throw new Error(UNEXPECTED_FORMAT_ERROR); | 
            
                                                        
            
                                    
            
            
                | 247 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 248 |  |  |     } catch (error) { | 
            
                                                        
            
                                    
            
            
                | 249 |  |  |       if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 250 |  |  |         dispatch({ | 
            
                                                        
            
                                    
            
            
                | 251 |  |  |           type: ActionTypes.IndexReject, | 
            
                                                        
            
                                    
            
            
                | 252 |  |  |           payload: error, | 
            
                                                        
            
                                    
            
            
                | 253 |  |  |         }); | 
            
                                                        
            
                                    
            
            
                | 254 |  |  |         handleError(error); | 
            
                                                        
            
                                    
            
            
                | 255 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 256 |  |  |       throw error; | 
            
                                                        
            
                                    
            
            
                | 257 |  |  |     } | 
            
                                                        
            
                                    
            
            
                | 258 |  |  |     if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 259 |  |  |       dispatch({ | 
            
                                                        
            
                                    
            
            
                | 260 |  |  |         type: ActionTypes.IndexFulfill, | 
            
                                                        
            
                                    
            
            
                | 261 |  |  |         payload: index.map(addKey), | 
            
                                                        
            
                                    
            
            
                | 262 |  |  |       }); | 
            
                                                        
            
                                    
            
            
                | 263 |  |  |     } | 
            
                                                        
            
                                    
            
            
                | 264 |  |  |     return index; | 
            
                                                        
            
                                    
            
            
                | 265 |  |  |   }, [endpoint, parseIndexResponse, handleError, addKey]); | 
            
                                                        
            
                                    
            
            
                | 266 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 267 |  |  |   const update = useCallback( | 
            
                                                        
            
                                    
            
            
                | 268 |  |  |     async (newValue: T): Promise<T> => { | 
            
                                                        
            
                                    
            
            
                | 269 |  |  |       const meta = addKey(newValue); | 
            
                                                        
            
                                    
            
            
                | 270 |  |  |       dispatch({ | 
            
                                                        
            
                                    
            
            
                | 271 |  |  |         type: ActionTypes.UpdateStart, | 
            
                                                        
            
                                    
            
            
                | 272 |  |  |         meta, | 
            
                                                        
            
                                    
            
            
                | 273 |  |  |       }); | 
            
                                                        
            
                                    
            
            
                | 274 |  |  |       let value: T; | 
            
                                                        
            
                                    
            
            
                | 275 |  |  |       try { | 
            
                                                        
            
                                    
            
            
                | 276 |  |  |         const json = await putRequest( | 
            
                                                        
            
                                    
            
            
                | 277 |  |  |           resolveEntityEndpoint(endpoint, newValue), | 
            
                                                        
            
                                    
            
            
                | 278 |  |  |           newValue, | 
            
                                                        
            
                                    
            
            
                | 279 |  |  |         ).then(processJsonResponse); | 
            
                                                        
            
                                    
            
            
                | 280 |  |  |         value = parseEntityResponse(json); | 
            
                                                        
            
                                    
            
            
                | 281 |  |  |         if (!isValidEntity(value)) { | 
            
                                                        
            
                                    
            
            
                | 282 |  |  |           throw new Error(UNEXPECTED_FORMAT_ERROR); | 
            
                                                        
            
                                    
            
            
                | 283 |  |  |         } | 
            
                                                        
            
                                    
            
            
                | 284 |  |  |       } catch (error) { | 
            
                                                        
            
                                    
            
            
                | 285 |  |  |         if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 286 |  |  |           dispatch({ | 
            
                                                        
            
                                    
            
            
                | 287 |  |  |             type: ActionTypes.UpdateReject, | 
            
                                                        
            
                                    
            
            
                | 288 |  |  |             payload: error, | 
            
                                                        
            
                                    
            
            
                | 289 |  |  |             meta, | 
            
                                                        
            
                                    
            
            
                | 290 |  |  |           }); | 
            
                                                        
            
                                    
            
            
                | 291 |  |  |           handleError(error); | 
            
                                                        
            
                                    
            
            
                | 292 |  |  |         } | 
            
                                                        
            
                                    
            
            
                | 293 |  |  |         throw error; | 
            
                                                        
            
                                    
            
            
                | 294 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 295 |  |  |       if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 296 |  |  |         dispatch({ | 
            
                                                        
            
                                    
            
            
                | 297 |  |  |           type: ActionTypes.UpdateFulfill, | 
            
                                                        
            
                                    
            
            
                | 298 |  |  |           payload: value, | 
            
                                                        
            
                                    
            
            
                | 299 |  |  |           meta, | 
            
                                                        
            
                                    
            
            
                | 300 |  |  |         }); | 
            
                                                        
            
                                    
            
            
                | 301 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 302 |  |  |       return value; | 
            
                                                        
            
                                    
            
            
                | 303 |  |  |     }, | 
            
                                                        
            
                                    
            
            
                | 304 |  |  |     [endpoint, resolveEntityEndpoint, parseEntityResponse, handleError, addKey], | 
            
                                                        
            
                                    
            
            
                | 305 |  |  |   ); | 
            
                                                        
            
                                    
            
            
                | 306 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 307 |  |  |   const deleteResource = useCallback( | 
            
                                                        
            
                                    
            
            
                | 308 |  |  |     async (entity: T): Promise<void> => { | 
            
                                                        
            
                                    
            
            
                | 309 |  |  |       const meta = { key: keyFn(entity) }; | 
            
                                                        
            
                                    
            
            
                | 310 |  |  |       dispatch({ | 
            
                                                        
            
                                    
            
            
                | 311 |  |  |         type: ActionTypes.DeleteStart, | 
            
                                                        
            
                                    
            
            
                | 312 |  |  |         meta, | 
            
                                                        
            
                                    
            
            
                | 313 |  |  |       }); | 
            
                                                        
            
                                    
            
            
                | 314 |  |  |       try { | 
            
                                                        
            
                                    
            
            
                | 315 |  |  |         const response = await deleteRequest( | 
            
                                                        
            
                                    
            
            
                | 316 |  |  |           resolveEntityEndpoint(endpoint, entity), | 
            
                                                        
            
                                    
            
            
                | 317 |  |  |         ); | 
            
                                                        
            
                                    
            
            
                | 318 |  |  |         if (!response.ok) { | 
            
                                                        
            
                                    
            
            
                | 319 |  |  |           throw new FetchError(response); | 
            
                                                        
            
                                    
            
            
                | 320 |  |  |         } | 
            
                                                        
            
                                    
            
            
                | 321 |  |  |       } catch (error) { | 
            
                                                        
            
                                    
            
            
                | 322 |  |  |         if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 323 |  |  |           dispatch({ | 
            
                                                        
            
                                    
            
            
                | 324 |  |  |             type: ActionTypes.DeleteReject, | 
            
                                                        
            
                                    
            
            
                | 325 |  |  |             payload: error, | 
            
                                                        
            
                                    
            
            
                | 326 |  |  |             meta, | 
            
                                                        
            
                                    
            
            
                | 327 |  |  |           }); | 
            
                                                        
            
                                    
            
            
                | 328 |  |  |           handleError(error); | 
            
                                                        
            
                                    
            
            
                | 329 |  |  |         } | 
            
                                                        
            
                                    
            
            
                | 330 |  |  |         throw error; | 
            
                                                        
            
                                    
            
            
                | 331 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 332 |  |  |       if (isSubscribed.current) { | 
            
                                                        
            
                                    
            
            
                | 333 |  |  |         dispatch({ | 
            
                                                        
            
                                    
            
            
                | 334 |  |  |           type: ActionTypes.DeleteFulfill, | 
            
                                                        
            
                                    
            
            
                | 335 |  |  |           meta, | 
            
                                                        
            
                                    
            
            
                | 336 |  |  |         }); | 
            
                                                        
            
                                    
            
            
                | 337 |  |  |       } | 
            
                                                        
            
                                    
            
            
                | 338 |  |  |     }, | 
            
                                                        
            
                                    
            
            
                | 339 |  |  |     [endpoint, resolveEntityEndpoint, handleError, keyFn], | 
            
                                                        
            
                                    
            
            
                | 340 |  |  |   ); | 
            
                                                        
            
                                    
            
            
                | 341 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 342 |  |  |   // Despite the usual guidelines, this should only be reconsidered if endpoint changes. | 
            
                                                        
            
                                    
            
            
                | 343 |  |  |   // Changing doInitialRefresh after the first run (or refresh) should not cause this to rerun. | 
            
                                                        
            
                                    
            
            
                | 344 |  |  |   useEffect(() => { | 
            
                                                        
            
                                    
            
            
                | 345 |  |  |     if (doInitialRefresh) { | 
            
                                                        
            
                                    
            
            
                | 346 |  |  |       refresh().catch(doNothing); | 
            
                                                        
            
                                    
            
            
                | 347 |  |  |     } | 
            
                                                        
            
                                    
            
            
                | 348 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 349 |  |  |     // Unsubscribe from promises when this hook is unmounted. | 
            
                                                        
            
                                    
            
            
                | 350 |  |  |     return (): void => { | 
            
                                                        
            
                                    
            
            
                | 351 |  |  |       isSubscribed.current = false; | 
            
                                                        
            
                                    
            
            
                | 352 |  |  |     }; | 
            
                                                        
            
                                    
            
            
                | 353 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 354 |  |  |     // eslint-disable-next-line react-hooks/exhaustive-deps | 
            
                                                        
            
                                    
            
            
                | 355 |  |  |   }, [endpoint]); | 
            
                                                        
            
                                    
            
            
                | 356 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 357 |  |  |   return { | 
            
                                                        
            
                                    
            
            
                | 358 |  |  |     values, | 
            
                                                        
            
                                    
            
            
                | 359 |  |  |     indexStatus, | 
            
                                                        
            
                                    
            
            
                | 360 |  |  |     createStatus, | 
            
                                                        
            
                                    
            
            
                | 361 |  |  |     entityStatus, | 
            
                                                        
            
                                    
            
            
                | 362 |  |  |     create, | 
            
                                                        
            
                                    
            
            
                | 363 |  |  |     refresh, | 
            
                                                        
            
                                    
            
            
                | 364 |  |  |     update, | 
            
                                                        
            
                                    
            
            
                | 365 |  |  |     deleteResource, | 
            
                                                        
            
                                    
            
            
                | 366 |  |  |   }; | 
            
                                                        
            
                                    
            
            
                | 367 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 368 |  |  |  | 
            
                                                        
            
                                    
            
            
                | 369 |  |  | export default useResourceIndex; | 
            
                                                        
            
                                    
            
            
                | 370 |  |  |  |