fix(client): only fetch completion data on challenge pages (#55787)
parent
119bfdf9b9
commit
d5f109ac4e
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { TFunction } from 'i18next';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
|
||||
import {
|
||||
challengeMetaSelector,
|
||||
currentBlockIdsSelector,
|
||||
|
@ -10,6 +12,8 @@ import {
|
|||
completedPercentageSelector
|
||||
} from '../../templates/Challenges/redux/selectors';
|
||||
import { liveCerts } from '../../../config/cert-and-project-map';
|
||||
import { updateAllChallengesInfo } from '../../redux/actions';
|
||||
import { CertificateNode, ChallengeNode } from '../../redux/prop-types';
|
||||
import ProgressInner from './progress-inner';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
|
@ -40,9 +44,11 @@ const mapStateToProps = createSelector(
|
|||
})
|
||||
);
|
||||
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
const mapDispatchToProps = { updateAllChallengesInfo };
|
||||
|
||||
interface ProgressProps extends StateProps {
|
||||
type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
interface ProgressProps extends PropsFromRedux {
|
||||
t: TFunction;
|
||||
}
|
||||
function Progress({
|
||||
|
@ -52,7 +58,8 @@ function Progress({
|
|||
superBlock,
|
||||
completedChallengesInBlock,
|
||||
completedPercent,
|
||||
t
|
||||
t,
|
||||
updateAllChallengesInfo
|
||||
}: ProgressProps): JSX.Element {
|
||||
const blockTitle = t(`intro:${superBlock}.blocks.${block}.title`);
|
||||
// Always false for legacy full stack, since it has no projects.
|
||||
|
@ -60,6 +67,11 @@ function Progress({
|
|||
cert.projects?.some((project: { id: string }) => project.id === id)
|
||||
);
|
||||
|
||||
const { challengeNodes, certificateNodes } = useGetAllBlockIds();
|
||||
useEffect(() => {
|
||||
updateAllChallengesInfo({ challengeNodes, certificateNodes });
|
||||
}, [challengeNodes, certificateNodes, updateAllChallengesInfo]);
|
||||
|
||||
const totalChallengesInBlock = currentBlockIds?.length ?? 0;
|
||||
const meta =
|
||||
isCertificationProject && totalChallengesInBlock > 0
|
||||
|
@ -84,6 +96,53 @@ function Progress({
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: extract this hook and call it when needed (i.e. here, in the lower-jaw
|
||||
// and in completion-modal). Then we don't have to pass the data into redux.
|
||||
// This would mean that we have to memoize any complex calculations in the hook.
|
||||
// Otherwise, this will undo all the recent performance improvements.
|
||||
const useGetAllBlockIds = () => {
|
||||
const {
|
||||
allChallengeNode: { nodes: challengeNodes },
|
||||
allCertificateNode: { nodes: certificateNodes }
|
||||
}: {
|
||||
allChallengeNode: { nodes: ChallengeNode[] };
|
||||
allCertificateNode: { nodes: CertificateNode[] };
|
||||
} = useStaticQuery(graphql`
|
||||
query getBlockNode {
|
||||
allChallengeNode(
|
||||
sort: {
|
||||
fields: [
|
||||
challenge___superOrder
|
||||
challenge___order
|
||||
challenge___challengeOrder
|
||||
]
|
||||
}
|
||||
) {
|
||||
nodes {
|
||||
challenge {
|
||||
block
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
allCertificateNode {
|
||||
nodes {
|
||||
challenge {
|
||||
certification
|
||||
tests {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return { challengeNodes, certificateNodes };
|
||||
};
|
||||
|
||||
Progress.displayName = 'Progress';
|
||||
|
||||
export default connect(mapStateToProps)(withTranslation()(Progress));
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default connector(withTranslation()(Progress));
|
||||
|
|
|
@ -5,7 +5,6 @@ import { useMediaQuery } from 'react-responsive';
|
|||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
|
||||
import latoBoldURL from '../../../static/fonts/lato/Lato-Bold.woff';
|
||||
import latoLightURL from '../../../static/fonts/lato/Lato-Light.woff';
|
||||
|
@ -18,8 +17,7 @@ import { isBrowser } from '../../../utils';
|
|||
import {
|
||||
fetchUser,
|
||||
onlineStatusChange,
|
||||
serverStatusChange,
|
||||
updateAllChallengesInfo
|
||||
serverStatusChange
|
||||
} from '../../redux/actions';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
|
@ -30,12 +28,7 @@ import {
|
|||
userFetchStateSelector
|
||||
} from '../../redux/selectors';
|
||||
|
||||
import {
|
||||
UserFetchState,
|
||||
User,
|
||||
AllChallengeNode,
|
||||
CertificateNode
|
||||
} from '../../redux/prop-types';
|
||||
import { UserFetchState, User } from '../../redux/prop-types';
|
||||
import BreadCrumb from '../../templates/Challenges/components/bread-crumb';
|
||||
import Flash from '../Flash';
|
||||
import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
|
||||
|
@ -95,8 +88,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
|
|||
fetchUser,
|
||||
removeFlashMessage,
|
||||
onlineStatusChange,
|
||||
serverStatusChange,
|
||||
updateAllChallengesInfo
|
||||
serverStatusChange
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
@ -138,8 +130,7 @@ function DefaultLayout({
|
|||
superBlock,
|
||||
theme,
|
||||
user,
|
||||
fetchUser,
|
||||
updateAllChallengesInfo
|
||||
fetchUser
|
||||
}: DefaultLayoutProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const isMobileLayout = useMediaQuery({ maxWidth: MAX_MOBILE_WIDTH });
|
||||
|
@ -150,10 +141,8 @@ function DefaultLayout({
|
|||
const isExSmallViewportHeight = useMediaQuery({
|
||||
maxHeight: EX_SMALL_VIEWPORT_HEIGHT
|
||||
});
|
||||
const { challengeEdges, certificateNodes } = useGetAllBlockIds();
|
||||
useEffect(() => {
|
||||
// componentDidMount
|
||||
updateAllChallengesInfo({ challengeEdges, certificateNodes });
|
||||
if (!isSignedIn) {
|
||||
fetchUser();
|
||||
}
|
||||
|
@ -278,50 +267,6 @@ function DefaultLayout({
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: get challenge nodes directly rather than wrapped in edges
|
||||
const useGetAllBlockIds = () => {
|
||||
const {
|
||||
allChallengeNode: { edges: challengeEdges },
|
||||
allCertificateNode: { nodes: certificateNodes }
|
||||
}: {
|
||||
allChallengeNode: AllChallengeNode;
|
||||
allCertificateNode: { nodes: CertificateNode[] };
|
||||
} = useStaticQuery(graphql`
|
||||
query getBlockNode {
|
||||
allChallengeNode(
|
||||
sort: {
|
||||
fields: [
|
||||
challenge___superOrder
|
||||
challenge___order
|
||||
challenge___challengeOrder
|
||||
]
|
||||
}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
block
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allCertificateNode {
|
||||
nodes {
|
||||
challenge {
|
||||
certification
|
||||
tests {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return { challengeEdges, certificateNodes };
|
||||
};
|
||||
|
||||
DefaultLayout.displayName = 'DefaultLayout';
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -66,7 +66,7 @@ const initialState = {
|
|||
...defaultFetchState
|
||||
},
|
||||
allChallengesInfo: {
|
||||
challengeEdges: [],
|
||||
challengeNodes: [],
|
||||
certificateNodes: []
|
||||
},
|
||||
userProfileFetchState: {
|
||||
|
|
|
@ -243,7 +243,7 @@ export type CertificateNode = {
|
|||
};
|
||||
|
||||
export type AllChallengesInfo = {
|
||||
challengeEdges: { node: ChallengeNode }[];
|
||||
challengeNodes: ChallengeNode[];
|
||||
certificateNodes: CertificateNode[];
|
||||
};
|
||||
|
||||
|
|
|
@ -8,11 +8,8 @@ import { createSelector } from 'reselect';
|
|||
import { Button, Modal } from '@freecodecamp/ui';
|
||||
|
||||
import Login from '../../../components/Header/components/login';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
allChallengesInfoSelector
|
||||
} from '../../../redux/selectors';
|
||||
import { AllChallengesInfo, ChallengeFiles } from '../../../redux/prop-types';
|
||||
import { isSignedInSelector } from '../../../redux/selectors';
|
||||
import { ChallengeFiles } from '../../../redux/prop-types';
|
||||
import { closeModal, submitChallenge } from '../redux/actions';
|
||||
import {
|
||||
completedChallengesIdsSelector,
|
||||
|
@ -35,7 +32,6 @@ const mapStateToProps = createSelector(
|
|||
completedChallengesIdsSelector,
|
||||
isCompletionModalOpenSelector,
|
||||
isSignedInSelector,
|
||||
allChallengesInfoSelector,
|
||||
successMessageSelector,
|
||||
isSubmittingSelector,
|
||||
(
|
||||
|
@ -44,7 +40,6 @@ const mapStateToProps = createSelector(
|
|||
completedChallengesIds: string[],
|
||||
isOpen: boolean,
|
||||
isSignedIn: boolean,
|
||||
allChallengesInfo: AllChallengesInfo,
|
||||
message: string,
|
||||
isSubmitting: boolean
|
||||
) => ({
|
||||
|
@ -54,7 +49,6 @@ const mapStateToProps = createSelector(
|
|||
completedChallengesIds,
|
||||
isOpen,
|
||||
isSignedIn,
|
||||
allChallengesInfo,
|
||||
message,
|
||||
isSubmitting
|
||||
})
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('completionEpic', () => {
|
|||
challenge: { challengeMeta: { challengeType: 0 } },
|
||||
app: {
|
||||
user: { username: 'test' },
|
||||
allChallengesInfo: { challengeEdges: [], certificateNodes: [] }
|
||||
allChallengesInfo: { challengeNodes: [], certificateNodes: [] }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -39,14 +39,14 @@ export const getCurrentBlockIds = (
|
|||
certification: string,
|
||||
challengeType: number
|
||||
): string[] => {
|
||||
const { challengeEdges, certificateNodes } = allChallengesInfo;
|
||||
const { challengeNodes, certificateNodes } = allChallengesInfo;
|
||||
const currentCertificateIds =
|
||||
certificateNodes
|
||||
.filter(node => node.challenge.certification === certification)[0]
|
||||
?.challenge.tests.map(test => test.id) ?? [];
|
||||
const currentBlockIds = challengeEdges
|
||||
.filter(edge => edge.node.challenge.block === block)
|
||||
.map(edge => edge.node.challenge.id);
|
||||
const currentBlockIds = challengeNodes
|
||||
.filter(node => node.challenge.block === block)
|
||||
.map(node => node.challenge.id);
|
||||
|
||||
return isProjectBased(challengeType)
|
||||
? currentCertificateIds
|
||||
|
|
Loading…
Reference in New Issue