diff --git a/src/constants.js b/src/constants.js
new file mode 100644
index 0000000..9e672c5
--- /dev/null
+++ b/src/constants.js
@@ -0,0 +1,4 @@
+export const IDLE_STATUS = 'idle';
+export const LOADING_STATUS = 'loading';
+export const SUCCESS_STATUS = 'success';
+export const FAILURE_STATUS = 'failure';
diff --git a/src/hooks.js b/src/hooks.js
index 6cec54c..aed3fd9 100644
--- a/src/hooks.js
+++ b/src/hooks.js
@@ -1,23 +1,31 @@
import { useEffect, useState } from 'react';
+import {
+ IDLE_STATUS, LOADING_STATUS, SUCCESS_STATUS, FAILURE_STATUS,
+} from './constants';
+
// eslint-disable-next-line import/prefer-default-export
export function useAsyncCall(asyncFunc) {
- const [isLoading, setIsLoading] = useState();
- const [data, setData] = useState();
+ // React doesn't batch setStates call in async useEffect hooks,
+ // so we use a combined object here to ensure that users
+ // re-render once.
+ const [data, setData] = useState({ status: IDLE_STATUS });
useEffect(
() => {
(async () => {
- setIsLoading(true);
+ setData(currData => ({ ...currData, status: LOADING_STATUS }));
const response = await asyncFunc();
- setIsLoading(false);
- if (response) {
- setData(response);
+
+ if (Object.keys(response).length === 0) {
+ setData(currData => ({ ...currData, status: FAILURE_STATUS, data: response }));
+ } else {
+ setData(currData => ({ ...currData, status: SUCCESS_STATUS, data: response }));
}
})();
},
[asyncFunc],
);
- return [isLoading, data];
+ return data;
}
diff --git a/src/id-verification/IdVerificationContextProvider.jsx b/src/id-verification/IdVerificationContextProvider.jsx
index 4acd4a8..9d4233c 100644
--- a/src/id-verification/IdVerificationContextProvider.jsx
+++ b/src/id-verification/IdVerificationContextProvider.jsx
@@ -5,6 +5,7 @@ import { AppContext } from '@edx/frontend-platform/react';
import { getProfileDataManager } from '../account-settings/data/service';
import PageLoading from '../account-settings/PageLoading';
import { useAsyncCall } from '../hooks';
+import { IDLE_STATUS, LOADING_STATUS, SUCCESS_STATUS } from '../constants';
import { getExistingIdVerification, getEnrollments } from './data/service';
import AccessBlocked from './AccessBlocked';
@@ -14,17 +15,10 @@ import { VerifiedNameContext } from './VerifiedNameContext';
export default function IdVerificationContextProvider({ children }) {
const { authenticatedUser } = useContext(AppContext);
- const { isVerifiedNameHistoryLoading, verifiedName, verifiedNameEnabled } = useContext(VerifiedNameContext);
+ const { verifiedNameHistoryCallStatus, verifiedName, verifiedNameEnabled } = useContext(VerifiedNameContext);
- // Call verification status endpoint to check whether we can verify.
- const [existingIdVerification, setExistingIdVerification] = useState(null);
- const [isIDVerificationLoading, idVerificationData] = useAsyncCall(getExistingIdVerification);
- const [isEnrollmentsLoading, enrollmentsData] = useAsyncCall(getEnrollments);
- useEffect(() => {
- if (idVerificationData) {
- setExistingIdVerification(idVerificationData);
- }
- }, [idVerificationData]);
+ const idVerificationData = useAsyncCall(getExistingIdVerification);
+ const enrollmentsData = useAsyncCall(getEnrollments);
const [facePhotoFile, setFacePhotoFile] = useState(null);
const [idPhotoFile, setIdPhotoFile] = useState(null);
@@ -34,36 +28,6 @@ export default function IdVerificationContextProvider({ children }) {
hasGetUserMediaSupport ? MEDIA_ACCESS.PENDING : MEDIA_ACCESS.UNSUPPORTED,
);
- const [canVerify, setCanVerify] = useState(true);
- const [error, setError] = useState('');
- useEffect(() => {
- // With verified name we can redo verification multiple times
- // if not a successful request prevents re-verification
- if (!verifiedNameEnabled && existingIdVerification && !existingIdVerification.canVerify) {
- const { status } = existingIdVerification;
- setCanVerify(false);
- if (status === 'pending' || status === 'approved') {
- setError(ERROR_REASONS.EXISTING_REQUEST);
- } else {
- setError(ERROR_REASONS.CANNOT_VERIFY);
- }
- } else if (verifiedNameEnabled) {
- setCanVerify(true);
- }
- }, [existingIdVerification, verifiedNameEnabled]);
-
- useEffect(() => {
- if (!isEnrollmentsLoading && enrollmentsData) {
- const verifiedEnrollments = enrollmentsData.filter((enrollment) => (
- VERIFIED_MODES.includes(enrollment.mode)
- ));
- if (verifiedEnrollments.length === 0) {
- setCanVerify(false);
- setError(ERROR_REASONS.COURSE_ENROLLMENT);
- }
- }
- }, [enrollmentsData]);
-
const [profileDataManager, setProfileDataManager] = useState(null);
useEffect(() => {
// Determine if the user's profile data is managed by a third-party identity provider.
@@ -92,6 +56,40 @@ export default function IdVerificationContextProvider({ children }) {
// this flag ensures that they are directed straight back to the summary panel
const [reachedSummary, setReachedSummary] = useState(false);
+ let canVerify = false;
+ let error = '';
+ let existingIdVerification;
+
+ if (idVerificationData?.data) {
+ existingIdVerification = idVerificationData.data;
+ }
+
+ if (verifiedNameHistoryCallStatus === SUCCESS_STATUS && idVerificationData.status === SUCCESS_STATUS) {
+ // With verified name we can redo verification multiple times
+ // if not a successful request prevents re-verification
+ if (!verifiedNameEnabled && existingIdVerification && !existingIdVerification.canVerify) {
+ const { status } = existingIdVerification;
+ canVerify = false;
+ if (status === 'pending' || status === 'approved') {
+ error = ERROR_REASONS.EXISTING_REQUEST;
+ } else {
+ error = ERROR_REASONS.CANNOT_VERIFY;
+ }
+ } else if (verifiedNameEnabled) {
+ canVerify = true;
+ }
+ }
+
+ if (enrollmentsData.status === SUCCESS_STATUS && enrollmentsData?.data) {
+ const verifiedEnrollments = enrollmentsData.data.filter((enrollment) => (
+ VERIFIED_MODES.includes(enrollment.mode)
+ ));
+ if (verifiedEnrollments.length === 0) {
+ canVerify = false;
+ error = ERROR_REASONS.COURSE_ENROLLMENT;
+ }
+ }
+
const contextValue = {
existingIdVerification,
facePhotoFile,
@@ -109,7 +107,6 @@ export default function IdVerificationContextProvider({ children }) {
portraitPhotoMode,
idPhotoMode,
reachedSummary,
- setExistingIdVerification,
setFacePhotoFile,
setIdPhotoFile,
setIdPhotoName,
@@ -141,8 +138,9 @@ export default function IdVerificationContextProvider({ children }) {
},
};
- // If we are waiting for verification status endpoint, show spinner.
- if (isIDVerificationLoading || isVerifiedNameHistoryLoading) {
+ const loadingStatuses = [IDLE_STATUS, LOADING_STATUS];
+ // If we are waiting for verification status or verified name history endpoint, show spinner.
+ if (loadingStatuses.includes(idVerificationData.status) || loadingStatuses.includes(verifiedNameHistoryCallStatus)) {
return