Passed
Push — task/copy-profile-data-to-new-... ( 2f8460...653b18 )
by Grant
04:05
created

applicationHooks.tsx ➔ useFetchAllApplicationData   B

Complexity

Conditions 2

Size

Total Lines 57
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 47
c 0
b 0
f 0
dl 0
loc 57
rs 8.7345
cc 2

How to fix   Long Method   

Long Method

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:

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