Passed
Push — task/refactor-application-data... ( 2cf671...44cf03 )
by Tristan
04:07
created

applicationHooks.tsx ➔ useCriteria   A

Complexity

Conditions 2

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
1
/* eslint-disable camelcase */
2
import { useCallback, useEffect, useState } from "react";
3
import { useSelector } from "react-redux";
4
import { DispatchType } from "../../configureStore";
5
import { RootState } from "../../store/store";
6
import { getAwardRecipientTypes as fetchAwardRecipientTypes } from "../../store/AwardRecipientType/awardRecipientTypeActions";
7
import { getAwardRecipientTypes } from "../../store/AwardRecipientType/awardRecipientTypeSelector";
8
import { getAwardRecognitionTypes as fetchAwardRecognitionTypes } from "../../store/AwardRecognitionType/awardRecognitionTypeActions";
9
import { getAwardRecognitionTypes } from "../../store/AwardRecognitionType/awardRecognitionTypeSelector";
10
import { getEducationTypes as fetchEducationTypes } from "../../store/EducationType/educationTypeActions";
11
import { getEducationTypes } from "../../store/EducationType/educationTypeSelector";
12
import { getEducationStatuses as fetchEducationStatuses } from "../../store/EducationStatus/educationStatusActions";
13
import { getEducationStatuses } from "../../store/EducationStatus/educationStatusSelector";
14
import {
15
  AwardRecipientType,
16
  AwardRecognitionType,
17
  EducationType,
18
  EducationStatus,
19
  Job,
20
  Experience as ExperienceType,
21
  Skill,
22
  ExperienceSkill,
23
  ApplicationNormalized,
24
  Criteria,
25
} from "../../models/types";
26
import {
27
  getApplicationIsUpdating,
28
  getApplicationNormalized,
29
} from "../../store/Application/applicationSelector";
30
import { fetchApplicationNormalized } from "../../store/Application/applicationActions";
31
import {
32
  getCriteriaByJob,
33
  getJob,
34
  getJobIsUpdating,
35
} from "../../store/Job/jobSelector";
36
import { fetchJob } from "../../store/Job/jobActions";
37
import { ApplicationStatusId } from "../../models/lookupConstants";
38
import {
39
  getExperienceByApplicant,
40
  getExperienceByApplication,
41
  getExperienceSkillsByApplicant,
42
  getExperienceSkillsByApplication,
43
  getUpdatingByApplicant,
44
  getUpdatingByApplication,
45
} from "../../store/Experience/experienceSelector";
46
import {
47
  fetchExperienceByApplicant,
48
  fetchExperienceByApplication,
49
} from "../../store/Experience/experienceActions";
50
import { getSkills, getSkillsUpdating } from "../../store/Skill/skillSelector";
51
import { fetchSkills } from "../../store/Skill/skillActions";
52
53
export function useApplication(
54
  applicationId: number,
55
): ApplicationNormalized | null {
56
  return useSelector((state: RootState) =>
57
    getApplicationNormalized(state, { applicationId }),
58
  );
59
}
60
61
export function useJob(jobId: number | undefined): Job | null {
62
  return useSelector((state: RootState) =>
63
    jobId ? getJob(state, { jobId }) : null,
64
  );
65
}
66
67
export function useExperienceConstants(): {
68
  awardRecipientTypes: AwardRecipientType[];
69
  awardRecognitionTypes: AwardRecognitionType[];
70
  educationTypes: EducationType[];
71
  educationStatuses: EducationStatus[];
72
} {
73
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
74
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
75
  const educationTypes = useSelector(getEducationTypes);
76
  const educationStatuses = useSelector(getEducationStatuses);
77
  return {
78
    awardRecipientTypes,
79
    awardRecognitionTypes,
80
    educationTypes,
81
    educationStatuses,
82
  };
83
}
84
85
export function useSkills(): Skill[] {
86
  return useSelector(getSkills);
87
}
88
89
export function useCriteria(jobId: number | undefined): Criteria[] {
90
  return useSelector((state: RootState) =>
91
    jobId ? getCriteriaByJob(state, { jobId }) : [],
92
  );
93
}
94
95
export function useExperiences(
96
  applicationId: number,
97
  application: ApplicationNormalized | null,
98
): ExperienceType[] {
99
  const applicantId = application?.applicant_id ?? 0;
100
101
  // When an Application is still a draft, use Experiences associated with the applicant profile.
102
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
103
  const useProfileExperience =
104
    application === null ||
105
    application.application_status_id === ApplicationStatusId.draft;
106
107
  // This selector must be memoized because getExperienceByApplicant/Application uses reselect, and not re-reselect, so it needs to preserve its state.
108
  const experienceSelector = useCallback(
109
    (state: RootState) =>
110
      useProfileExperience
111
        ? getExperienceByApplicant(state, { applicantId })
112
        : getExperienceByApplication(state, { applicationId }),
113
    [applicationId, applicantId, useProfileExperience],
114
  );
115
  const experiencesByType = useSelector(experienceSelector);
116
  const experiences: ExperienceType[] = [
117
    ...experiencesByType.award,
118
    ...experiencesByType.community,
119
    ...experiencesByType.education,
120
    ...experiencesByType.personal,
121
    ...experiencesByType.work,
122
  ];
123
  return experiences;
124
}
125
126
export function useExperienceSkills(
127
  applicationId: number,
128
  application: ApplicationNormalized | null,
129
): ExperienceSkill[] {
130
  // ExperienceSkills don't need to be fetched because they are returned in the Experiences API calls.
131
  const applicantId = application?.applicant_id ?? 0;
132
  const useProfileExperience =
133
    application === null ||
134
    application.application_status_id === ApplicationStatusId.draft;
135
  const expSkillSelector = (state: RootState) =>
136
    useProfileExperience
137
      ? getExperienceSkillsByApplicant(state, { applicantId })
138
      : getExperienceSkillsByApplication(state, { applicationId });
139
  const experienceSkills = useSelector(expSkillSelector);
140
  return experienceSkills;
141
}
142
143
/**
144
 * Return all skills from the redux store, and fetch the skills from backend if they are not yet in the store.
145
 * @param dispatch
146
 */
147
export function useFetchSkills(dispatch: DispatchType): Skill[] {
148
  const skills = useSelector(getSkills);
149
  const skillsUpdating = useSelector(getSkillsUpdating);
150
  useEffect(() => {
151
    if (skills.length === 0 && !skillsUpdating) {
152
      dispatch(fetchSkills());
153
    }
154
  }, [skills.length, skillsUpdating, dispatch]);
155
  return skills;
156
}
157
158
/**
159
 * Return all Experience constants from the redux store, and fetch them from backend if they are not yet in the store.
160
 * @param dispatch
161
 */
162
export function useFetchExperienceConstants(
163
  dispatch: DispatchType,
164
): {
165
  awardRecipientTypes: AwardRecipientType[];
166
  awardRecognitionTypes: AwardRecognitionType[];
167
  educationTypes: EducationType[];
168
  educationStatuses: EducationStatus[];
169
} {
170
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
171
  const awardRecipientTypesLoading = useSelector(
172
    (state: RootState) => state.awardRecipientType.loading,
173
  );
174
  useEffect(() => {
175
    if (awardRecipientTypes.length === 0 && !awardRecipientTypesLoading) {
176
      dispatch(fetchAwardRecipientTypes());
177
    }
178
  }, [awardRecipientTypes, awardRecipientTypesLoading, dispatch]);
179
180
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
181
  const awardRecognitionTypesLoading = useSelector(
182
    (state: RootState) => state.awardRecognitionType.loading,
183
  );
184
  useEffect(() => {
185
    if (awardRecognitionTypes.length === 0 && !awardRecognitionTypesLoading) {
186
      dispatch(fetchAwardRecognitionTypes());
187
    }
188
  }, [awardRecognitionTypes, awardRecognitionTypesLoading, dispatch]);
189
190
  const educationTypes = useSelector(getEducationTypes);
191
  const educationTypesLoading = useSelector(
192
    (state: RootState) => state.educationType.loading,
193
  );
194
  useEffect(() => {
195
    if (educationTypes.length === 0 && !educationTypesLoading) {
196
      dispatch(fetchEducationTypes());
197
    }
198
  }, [educationTypes, educationTypesLoading, dispatch]);
199
200
  const educationStatuses = useSelector(getEducationStatuses);
201
  const educationStatusesLoading = useSelector(
202
    (state: RootState) => state.educationStatus.loading,
203
  );
204
  useEffect(() => {
205
    if (educationStatuses.length === 0 && !educationStatusesLoading) {
206
      dispatch(fetchEducationStatuses());
207
    }
208
  }, [educationStatuses, educationStatusesLoading, dispatch]);
209
210
  return {
211
    awardRecipientTypes,
212
    awardRecognitionTypes,
213
    educationTypes,
214
    educationStatuses,
215
  };
216
}
217
218
/**
219
 * Return an Application (normalized, ie without Review) from the redux store, and fetch it from backend if it is not yet in the store.
220
 * @param applicationId
221
 * @param dispatch
222
 */
223
export function useFetchApplication(
224
  applicationId: number,
225
  dispatch: DispatchType,
226
): ApplicationNormalized | null {
227
  const applicationSelector = (
228
    state: RootState,
229
  ): ApplicationNormalized | null =>
230
    getApplicationNormalized(state, { applicationId });
231
  const application: ApplicationNormalized | null = useSelector(
232
    applicationSelector,
233
  );
234
  const applicationIsUpdating = useSelector((state: RootState) =>
235
    getApplicationIsUpdating(state, { applicationId }),
236
  );
237
  useEffect(() => {
238
    if (application === null && !applicationIsUpdating) {
239
      dispatch(fetchApplicationNormalized(applicationId));
240
    }
241
  }, [application, applicationId, applicationIsUpdating, dispatch]);
242
  return application;
243
}
244
245
/**
246
 * Return an Job from the redux store, and fetch it from backend if it is not yet in the store.
247
 * @param jobId
248
 * @param dispatch
249
 */
250
export function useFetchJob(
251
  jobId: number | undefined,
252
  dispatch: DispatchType,
253
): Job | null {
254
  const job = useJob(jobId);
255
  const jobUpdatingSelector = (state: RootState) =>
256
    jobId ? getJobIsUpdating(state, jobId) : false;
257
  const jobIsUpdating = useSelector(jobUpdatingSelector);
258
  useEffect(() => {
259
    // If job is null and not already updating, fetch it.
260
    if (jobId && job === null && !jobIsUpdating) {
261
      dispatch(fetchJob(jobId));
262
    }
263
  }, [jobId, job, jobIsUpdating, dispatch]);
264
  return job;
265
}
266
267
/**
268
 * Return all Experience relavant to an Application from the redux store, and fetch it from backend if it is not yet in the store.
269
 * @param applicationId
270
 * @param application
271
 * @param dispatch
272
 */
273
export function useFetchExperience(
274
  applicationId: number,
275
  application: ApplicationNormalized | null,
276
  dispatch: DispatchType,
277
): {
278
  experiences: ExperienceType[];
279
  experiencesUpdating: boolean;
280
  experiencesFetched: boolean;
281
} {
282
  const applicantId = application?.applicant_id ?? 0;
283
284
  // When an Application is still a draft, use Experiences associated with the applicant profile.
285
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
286
  const applicationLoaded = application !== null;
287
  const useProfileExperience =
288
    application === null ||
289
    application.application_status_id === ApplicationStatusId.draft;
290
291
  const experiences = useExperiences(applicationId, application);
292
  const experiencesUpdating = useSelector((state: RootState) =>
293
    useProfileExperience
294
      ? getUpdatingByApplicant(state, { applicantId })
295
      : getUpdatingByApplication(state, { applicationId }),
296
  );
297
  const [experiencesFetched, setExperiencesFetched] = useState(false);
298
  useEffect(() => {
299
    // Only load experiences if they have never been fetched by this component (!experiencesFetched),
300
    //  have never been fetched by another component (length === 0),
301
    //  and are not currently being fetched (!experiencesUpdating).
302
    // Also, wait until application has been loaded so the correct source can be determined.
303
    if (
304
      applicationLoaded &&
305
      !experiencesFetched &&
306
      !experiencesUpdating &&
307
      experiences.length === 0
308
    ) {
309
      setExperiencesFetched(true);
310
      if (useProfileExperience) {
311
        dispatch(fetchExperienceByApplicant(applicantId));
312
      } else {
313
        dispatch(fetchExperienceByApplication(applicationId));
314
      }
315
    }
316
  }, [
317
    applicantId,
318
    applicationId,
319
    applicationLoaded,
320
    dispatch,
321
    experiences.length,
322
    experiencesFetched,
323
    experiencesUpdating,
324
    useProfileExperience,
325
  ]);
326
  return {
327
    experiences,
328
    experiencesUpdating,
329
    experiencesFetched,
330
  };
331
}
332
333
/**
334
 * Trigger fetches for all data needed for the Application process which is not yet in the redux store, or in the process of loading.
335
 * @param applicationId
336
 */
337
export function useFetchAllApplicationData(
338
  applicationId: number,
339
  dispatch: DispatchType,
340
): {
341
  applicationLoaded: boolean;
342
  jobLoaded: boolean;
343
  experiencesLoaded: boolean;
344
  experienceConstantsLoaded: boolean;
345
  skillsLoaded: boolean;
346
} {
347
  const application = useFetchApplication(applicationId, dispatch);
348
  const jobId = application?.job_poster_id;
349
  const job = useFetchJob(jobId, dispatch);
350
  const { experiences, experiencesUpdating } = useFetchExperience(
351
    applicationId,
352
    application,
353
    dispatch,
354
  );
355
  const {
356
    awardRecipientTypes,
357
    awardRecognitionTypes,
358
    educationTypes,
359
    educationStatuses,
360
  } = useFetchExperienceConstants(dispatch);
361
  const skills = useFetchSkills(dispatch);
362
363
  return {
364
    applicationLoaded: application !== null,
365
    jobLoaded: job !== null,
366
    experiencesLoaded: !experiencesUpdating || experiences.length > 0,
367
    experienceConstantsLoaded:
368
      awardRecipientTypes.length > 0 &&
369
      awardRecognitionTypes.length > 0 &&
370
      educationTypes.length > 0 &&
371
      educationStatuses.length > 0,
372
    skillsLoaded: skills.length > 0,
373
  };
374
}
375