Passed
Push — task/manager-application-revie... ( aaf66e...7b9da5 )
by Yonathan
04:54
created

applicationHooks.tsx ➔ useFetchApplication   A

Complexity

Conditions 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 22
rs 9.7
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
  JobPosterQuestion,
26
  JobApplicationAnswer,
27
  Application,
28
  User,
29
} from "../models/types";
30
import {
31
  getApplicationById,
32
  getApplicationIsUpdating,
33
  getApplicationNormalized,
34
  getJobApplicationAnswers,
35
} from "../store/Application/applicationSelector";
36
import {
37
  fetchApplication,
38
  fetchApplicationNormalized,
39
} from "../store/Application/applicationActions";
40
import {
41
  getCriteriaByJob,
42
  getJob,
43
  getJobIsUpdating,
44
  getJobPosterQuestionsByJob,
45
} from "../store/Job/jobSelector";
46
import { fetchJob } from "../store/Job/jobActions";
47
import { ApplicationStatusId } from "../models/lookupConstants";
48
import {
49
  getExperienceByApplicant,
50
  getExperienceByApplication,
51
  getExperienceSkillsByApplicant,
52
  getExperienceSkillsByApplication,
53
  getUpdatingByApplicant,
54
  getUpdatingByApplication,
55
} from "../store/Experience/experienceSelector";
56
import {
57
  fetchExperienceByApplicant,
58
  fetchExperienceByApplication,
59
} from "../store/Experience/experienceActions";
60
import { getSkills, getSkillsUpdating } from "../store/Skill/skillSelector";
61
import { fetchSkills } from "../store/Skill/skillActions";
62
import { getUserById } from "../store/User/userSelector";
63
import { fetchUser } from "../store/User/userActions";
64
65
export function useUser(userId: number | undefined): User | null {
66
  return useSelector((state: RootState) =>
67
    userId ? getUserById(state, { userId }) : null,
68
  );
69
}
70
export function useApplication(
71
  applicationId: number,
72
): ApplicationNormalized | null {
73
  return useSelector((state: RootState) =>
74
    getApplicationNormalized(state, { applicationId }),
75
  );
76
}
77
78
export function useReviewedApplication(
79
  applicationId: number,
80
): Application | null {
81
  return useSelector((state: RootState) =>
82
    getApplicationById(state, { id: applicationId }),
83
  );
84
}
85
86
export function useJob(jobId: number | undefined): Job | null {
87
  return useSelector((state: RootState) =>
88
    jobId ? getJob(state, { jobId }) : null,
89
  );
90
}
91
92
export function useExperienceConstants(): {
93
  awardRecipientTypes: AwardRecipientType[];
94
  awardRecognitionTypes: AwardRecognitionType[];
95
  educationTypes: EducationType[];
96
  educationStatuses: EducationStatus[];
97
} {
98
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
99
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
100
  const educationTypes = useSelector(getEducationTypes);
101
  const educationStatuses = useSelector(getEducationStatuses);
102
  return {
103
    awardRecipientTypes,
104
    awardRecognitionTypes,
105
    educationTypes,
106
    educationStatuses,
107
  };
108
}
109
110
export function useSkills(): Skill[] {
111
  return useSelector(getSkills);
112
}
113
114
export function useCriteria(jobId: number | undefined): Criteria[] {
115
  return useSelector((state: RootState) =>
116
    jobId ? getCriteriaByJob(state, { jobId }) : [],
117
  );
118
}
119
120
export function useExperiences(
121
  applicationId: number,
122
  application: ApplicationNormalized | null,
123
): ExperienceType[] {
124
  const applicantId = application?.applicant_id ?? 0;
125
126
  // When an Application is still a draft, use Experiences associated with the applicant profile.
127
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
128
  const useProfileExperience =
129
    application === null ||
130
    application.application_status_id === ApplicationStatusId.draft;
131
132
  // This selector must be memoized because getExperienceByApplicant/Application uses reselect, and not re-reselect, so it needs to preserve its state.
133
  const experienceSelector = useCallback(
134
    (state: RootState) =>
135
      useProfileExperience
136
        ? getExperienceByApplicant(state, { applicantId })
137
        : getExperienceByApplication(state, { applicationId }),
138
    [applicationId, applicantId, useProfileExperience],
139
  );
140
  const experiencesByType = useSelector(experienceSelector);
141
  const experiences: ExperienceType[] = [
142
    ...experiencesByType.award,
143
    ...experiencesByType.community,
144
    ...experiencesByType.education,
145
    ...experiencesByType.personal,
146
    ...experiencesByType.work,
147
  ];
148
  return experiences;
149
}
150
151
export function useExperienceSkills(
152
  applicationId: number,
153
  application: ApplicationNormalized | null,
154
): ExperienceSkill[] {
155
  // ExperienceSkills don't need to be fetched because they are returned in the Experiences API calls.
156
  const applicantId = application?.applicant_id ?? 0;
157
  const useProfileExperience =
158
    application === null ||
159
    application.application_status_id === ApplicationStatusId.draft;
160
  const expSkillSelector = (state: RootState) =>
161
    useProfileExperience
162
      ? getExperienceSkillsByApplicant(state, { applicantId })
163
      : getExperienceSkillsByApplication(state, { applicationId });
164
  const experienceSkills = useSelector(expSkillSelector);
165
  return experienceSkills;
166
}
167
168
export function useJobPosterQuestions(
169
  jobId: number | undefined,
170
): JobPosterQuestion[] {
171
  return useSelector((state: RootState) =>
172
    jobId ? getJobPosterQuestionsByJob(state, { jobId }) : [],
173
  );
174
}
175
176
export function useJobApplicationAnswers(
177
  applicationId: number,
178
): JobApplicationAnswer[] {
179
  return useSelector((state: RootState) =>
180
    getJobApplicationAnswers(state, { applicationId }),
181
  );
182
}
183
184
/**
185
 * Return all skills from the redux store, and fetch the skills from backend if they are not yet in the store.
186
 * @param dispatch
187
 */
188
export function useFetchSkills(dispatch: DispatchType): Skill[] {
189
  const skills = useSelector(getSkills);
190
  const skillsUpdating = useSelector(getSkillsUpdating);
191
  useEffect(() => {
192
    if (skills.length === 0 && !skillsUpdating) {
193
      dispatch(fetchSkills());
194
    }
195
  }, [skills.length, skillsUpdating, dispatch]);
196
  return skills;
197
}
198
199
/**
200
 * Return all Experience constants from the redux store, and fetch them from backend if they are not yet in the store.
201
 * @param dispatch
202
 */
203
export function useFetchExperienceConstants(
204
  dispatch: DispatchType,
205
): {
206
  awardRecipientTypes: AwardRecipientType[];
207
  awardRecognitionTypes: AwardRecognitionType[];
208
  educationTypes: EducationType[];
209
  educationStatuses: EducationStatus[];
210
} {
211
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
212
  const awardRecipientTypesLoading = useSelector(
213
    (state: RootState) => state.awardRecipientType.loading,
214
  );
215
  useEffect(() => {
216
    if (awardRecipientTypes.length === 0 && !awardRecipientTypesLoading) {
217
      dispatch(fetchAwardRecipientTypes());
218
    }
219
  }, [awardRecipientTypes, awardRecipientTypesLoading, dispatch]);
220
221
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
222
  const awardRecognitionTypesLoading = useSelector(
223
    (state: RootState) => state.awardRecognitionType.loading,
224
  );
225
  useEffect(() => {
226
    if (awardRecognitionTypes.length === 0 && !awardRecognitionTypesLoading) {
227
      dispatch(fetchAwardRecognitionTypes());
228
    }
229
  }, [awardRecognitionTypes, awardRecognitionTypesLoading, dispatch]);
230
231
  const educationTypes = useSelector(getEducationTypes);
232
  const educationTypesLoading = useSelector(
233
    (state: RootState) => state.educationType.loading,
234
  );
235
  useEffect(() => {
236
    if (educationTypes.length === 0 && !educationTypesLoading) {
237
      dispatch(fetchEducationTypes());
238
    }
239
  }, [educationTypes, educationTypesLoading, dispatch]);
240
241
  const educationStatuses = useSelector(getEducationStatuses);
242
  const educationStatusesLoading = useSelector(
243
    (state: RootState) => state.educationStatus.loading,
244
  );
245
  useEffect(() => {
246
    if (educationStatuses.length === 0 && !educationStatusesLoading) {
247
      dispatch(fetchEducationStatuses());
248
    }
249
  }, [educationStatuses, educationStatusesLoading, dispatch]);
250
251
  return {
252
    awardRecipientTypes,
253
    awardRecognitionTypes,
254
    educationTypes,
255
    educationStatuses,
256
  };
257
}
258
259
/**
260
 * Return an Application (normalized, ie without Review) from the redux store, and fetch it from backend if it is not yet in the store.
261
 * @param applicationId
262
 * @param dispatch
263
 */
264
export function useFetchNormalizedApplication(
265
  applicationId: number,
266
  dispatch: DispatchType,
267
): ApplicationNormalized | null {
268
  const applicationSelector = (
269
    state: RootState,
270
  ): ApplicationNormalized | null =>
271
    getApplicationNormalized(state, { applicationId });
272
  const application: ApplicationNormalized | null = useSelector(
273
    applicationSelector,
274
  );
275
  const applicationIsUpdating = useSelector((state: RootState) =>
276
    getApplicationIsUpdating(state, { applicationId }),
277
  );
278
  useEffect(() => {
279
    if (application === null && !applicationIsUpdating) {
280
      dispatch(fetchApplicationNormalized(applicationId));
281
    }
282
  }, [application, applicationId, applicationIsUpdating, dispatch]);
283
  return application;
284
}
285
286
/**
287
 * Return an Application (normalized, ie without Review) from the redux store, and fetch it from backend if it is not yet in the store.
288
 * @param applicationId
289
 * @param dispatch
290
 */
291
export function useFetchApplication(
292
  applicationId: number,
293
  dispatch: DispatchType,
294
): Application | null {
295
  const applicationSelector = (state: RootState): Application | null =>
296
    getApplicationById(state, { id: applicationId });
297
  const application: Application | null = useSelector(applicationSelector);
298
  const applicationIsUpdating = useSelector((state: RootState) =>
299
    getApplicationIsUpdating(state, { applicationId }),
300
  );
301
  useEffect(() => {
302
    if (application === null && !applicationIsUpdating) {
303
      dispatch(fetchApplication(applicationId));
304
    }
305
  }, [application, applicationId, applicationIsUpdating, dispatch]);
306
  return application;
307
}
308
309
/**
310
 * Return an Job from the redux store, and fetch it from backend if it is not yet in the store.
311
 * @param jobId
312
 * @param dispatch
313
 */
314
export function useFetchJob(
315
  jobId: number | undefined,
316
  dispatch: DispatchType,
317
): Job | null {
318
  const job = useJob(jobId);
319
  const jobUpdatingSelector = (state: RootState) =>
320
    jobId ? getJobIsUpdating(state, jobId) : false;
321
  const jobIsUpdating = useSelector(jobUpdatingSelector);
322
  useEffect(() => {
323
    // If job is null and not already updating, fetch it.
324
    if (jobId && job === null && !jobIsUpdating) {
325
      dispatch(fetchJob(jobId));
326
    }
327
  }, [jobId, job, jobIsUpdating, dispatch]);
328
  return job;
329
}
330
331
/**
332
 * Return all Experience relavant to an Application from the redux store, and fetch it from backend if it is not yet in the store.
333
 * @param applicationId
334
 * @param application
335
 * @param dispatch
336
 */
337
export function useFetchExperience(
338
  applicationId: number,
339
  application: ApplicationNormalized | null,
340
  dispatch: DispatchType,
341
): {
342
  experiences: ExperienceType[];
343
  experiencesUpdating: boolean;
344
  experiencesFetched: boolean;
345
} {
346
  const applicantId = application?.applicant_id ?? 0;
347
348
  // When an Application is still a draft, use Experiences associated with the applicant profile.
349
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
350
  const applicationLoaded = application !== null;
351
  const useProfileExperience =
352
    application === null ||
353
    application.application_status_id === ApplicationStatusId.draft;
354
355
  const experiences = useExperiences(applicationId, application);
356
  const experiencesUpdating = useSelector((state: RootState) =>
357
    useProfileExperience
358
      ? getUpdatingByApplicant(state, { applicantId })
359
      : getUpdatingByApplication(state, { applicationId }),
360
  );
361
  const [experiencesFetched, setExperiencesFetched] = useState(false);
362
  useEffect(() => {
363
    // Only load experiences if they have never been fetched by this component (!experiencesFetched),
364
    //  have never been fetched by another component (length === 0),
365
    //  and are not currently being fetched (!experiencesUpdating).
366
    // Also, wait until application has been loaded so the correct source can be determined.
367
    if (
368
      applicationLoaded &&
369
      !experiencesFetched &&
370
      !experiencesUpdating &&
371
      experiences.length === 0
372
    ) {
373
      setExperiencesFetched(true);
374
      if (useProfileExperience) {
375
        dispatch(fetchExperienceByApplicant(applicantId));
376
      } else {
377
        dispatch(fetchExperienceByApplication(applicationId));
378
      }
379
    }
380
  }, [
381
    applicantId,
382
    applicationId,
383
    applicationLoaded,
384
    dispatch,
385
    experiences.length,
386
    experiencesFetched,
387
    experiencesUpdating,
388
    useProfileExperience,
389
  ]);
390
  return {
391
    experiences,
392
    experiencesUpdating,
393
    experiencesFetched,
394
  };
395
}
396
397
/**
398
 * Return an User from the redux store, and fetch it from backend if it is not yet in the store.
399
 * @param jobId
400
 * @param dispatch
401
 */
402
export function useFetchUser(
403
  userId: number,
404
  dispatch: DispatchType,
405
): User | null {
406
  const user = useUser(userId);
407
  useEffect(() => {
408
    // If job is null and not already updating, fetch it.
409
    if (userId) {
410
      dispatch(fetchUser(userId));
411
    }
412
  }, [userId, dispatch]);
413
  return user;
414
}
415
416
/**
417
 * Trigger fetches for all data needed for the Application process which is not yet in the redux store, or in the process of loading.
418
 * @param applicationId
419
 */
420
export function useFetchAllApplicationData(
421
  applicationId: number,
422
  dispatch: DispatchType,
423
): {
424
  applicationLoaded: boolean;
425
  jobLoaded: boolean;
426
  experiencesLoaded: boolean;
427
  experienceConstantsLoaded: boolean;
428
  skillsLoaded: boolean;
429
} {
430
  const application = useFetchNormalizedApplication(applicationId, dispatch);
431
  const jobId = application?.job_poster_id;
432
  const job = useFetchJob(jobId, dispatch);
433
  const { experiences, experiencesUpdating } = useFetchExperience(
434
    applicationId,
435
    application,
436
    dispatch,
437
  );
438
  const {
439
    awardRecipientTypes,
440
    awardRecognitionTypes,
441
    educationTypes,
442
    educationStatuses,
443
  } = useFetchExperienceConstants(dispatch);
444
  const skills = useFetchSkills(dispatch);
445
446
  return {
447
    applicationLoaded: application !== null,
448
    jobLoaded: job !== null,
449
    experiencesLoaded: !experiencesUpdating || experiences.length > 0,
450
    experienceConstantsLoaded:
451
      awardRecipientTypes.length > 0 &&
452
      awardRecognitionTypes.length > 0 &&
453
      educationTypes.length > 0 &&
454
      educationStatuses.length > 0,
455
    skillsLoaded: skills.length > 0,
456
  };
457
}
458
459
/**
460
 * Trigger fetches for all data needed for the Application review process which is not yet in the redux store, or in the process of loading.
461
 * @param applicationId
462
 */
463
export function useFetchReviewApplicationData(
464
  applicantUserId: number,
465
  applicationId: number,
466
  dispatch: DispatchType,
467
): {
468
  applicationLoaded: boolean;
469
  jobLoaded: boolean;
470
  experiencesLoaded: boolean;
471
  experienceConstantsLoaded: boolean;
472
  skillsLoaded: boolean;
473
  userLoaded: boolean;
474
} {
475
  const application = useFetchApplication(applicationId, dispatch);
476
  const jobId = application?.job_poster_id;
477
  const job = useFetchJob(jobId, dispatch);
478
  const { experiences, experiencesUpdating } = useFetchExperience(
479
    applicationId,
480
    application,
481
    dispatch,
482
  );
483
  const {
484
    awardRecipientTypes,
485
    awardRecognitionTypes,
486
    educationTypes,
487
    educationStatuses,
488
  } = useFetchExperienceConstants(dispatch);
489
  const skills = useFetchSkills(dispatch);
490
  const applicantUser = useFetchUser(applicantUserId, dispatch);
491
492
  return {
493
    applicationLoaded: application !== null,
494
    jobLoaded: job !== null,
495
    experiencesLoaded: !experiencesUpdating || experiences.length > 0,
496
    experienceConstantsLoaded:
497
      awardRecipientTypes.length > 0 &&
498
      awardRecognitionTypes.length > 0 &&
499
      educationTypes.length > 0 &&
500
      educationStatuses.length > 0,
501
    skillsLoaded: skills.length > 0,
502
    userLoaded: applicantUser !== null,
503
  };
504
}
505