Conditions | 23 |
Total Lines | 96 |
Code Lines | 84 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like indexResourceHook.ts ➔ useResourceIndex often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | import { useCallback, useEffect, useMemo, useReducer, useRef } from "react"; |
||
43 | |||
44 | export function useResourceIndex<T extends { id: number }>( |
||
45 | endpoint: string, |
||
46 | overrides?: { |
||
47 | initialValue?: T[]; // Defaults to an empty list. |
||
48 | forceInitialRefresh: boolean; // If you set an initialValue but also want to refresh immediately, set this to true. |
||
49 | parseIndexResponse?: (response: Json) => T[]; |
||
50 | parseEntityResponse?: (response: Json) => T; |
||
51 | resolveEntityEndpoint?: (baseEndpoint: string, entity: T) => string; |
||
52 | resolveCreateEndpoint?: (baseEndpoint: string, newEntity: T) => string; |
||
53 | handleError?: (error: Error | FetchError) => void; |
||
54 | }, |
||
55 | ): { |
||
56 | values: IndexedObject<T>; |
||
57 | indexStatus: ResourceStatus; |
||
58 | entityStatus: IndexedObject<ResourceStatus>; |
||
59 | create: (newValue: T) => Promise<T>; |
||
60 | refresh: () => Promise<T[]>; // Reloads the entire index. |
||
61 | update: (newValue: T) => Promise<T>; |
||
62 | deleteResource: (id: number) => Promise<void>; |
||
63 | } { |
||
64 | const initialValue = overrides?.initialValue ?? []; |
||
65 | const forceInitialRefresh = |
||
66 | overrides?.initialValue !== undefined && overrides?.forceInitialRefresh; |
||
67 | const parseIndexResponse = overrides?.parseIndexResponse ?? identity; |
||
68 | const parseEntityResponse = overrides?.parseEntityResponse ?? identity; |
||
69 | const resolveEntityEndpoint = |
||
70 | overrides?.resolveEntityEndpoint ?? |
||
71 | ((baseEndpoint, entity): string => `${baseEndpoint}/${entity.id}`); |
||
72 | const resolveCreateEndpoint = |
||
73 | overrides?.resolveCreateEndpoint ?? |
||
74 | ((baseEndpoint, _): string => baseEndpoint); |
||
75 | const handleError = |
||
76 | overrides?.handleError ?? |
||
77 | ((): void => { |
||
78 | /* Do nothing. */ |
||
79 | }); |
||
80 | |||
81 | const isSubscribed = useRef(true); |
||
82 | |||
83 | const [state, dispatch] = useReducer( |
||
84 | indexCrudReducer, |
||
85 | initialValue, |
||
86 | initializeState, |
||
87 | ); |
||
88 | |||
89 | const values = useMemo(() => valuesSelector(state), [state]); |
||
90 | const indexStatus = state.indexMeta.status; |
||
91 | const entityStatus = useMemo(() => statusSelector(state), [state]); |
||
92 | |||
93 | const create = useCallback( |
||
94 | async (newValue: T): Promise<T> => { |
||
95 | dispatch({ |
||
96 | type: ActionTypes.createStart, |
||
97 | meta: { item: newValue }, |
||
98 | }); |
||
99 | let json: Json; |
||
100 | try { |
||
101 | json = await postRequest( |
||
102 | resolveCreateEndpoint(endpoint, newValue), |
||
103 | newValue, |
||
104 | ).then(processJsonResponse); |
||
105 | } catch (error) { |
||
106 | dispatch({ |
||
107 | type: ActionTypes.createReject, |
||
108 | payload: error, |
||
109 | meta: { item: newValue }, |
||
110 | }); |
||
111 | } |
||
112 | const entity = parseEntityResponse(json) as T; |
||
113 | if (isSubscribed.current) { |
||
114 | dispatch({ |
||
115 | type: ActionTypes.createFulfill, |
||
116 | payload: entity, |
||
117 | meta: { item: newValue }, |
||
118 | }); |
||
119 | } |
||
120 | return entity; |
||
121 | }, |
||
122 | [endpoint, resolveCreateEndpoint, parseEntityResponse], |
||
123 | ); |
||
124 | |||
125 | // Unsubscribe from promises when this hook is unmounted. |
||
126 | useEffect(() => { |
||
127 | return (): void => { |
||
128 | isSubscribed.current = false; |
||
129 | }; |
||
130 | }, []); |
||
131 | |||
132 | return { |
||
133 | values, |
||
134 | indexStatus, |
||
135 | entityStatus, |
||
136 | create, |
||
137 | update, |
||
138 | refresh, |
||
139 | }; |
||
143 |