fix(client): only fetch completion data on challenge pages (#55787)

pull/55633/head
Oliver Eyton-Williams 2024-08-09 15:40:06 +02:00 committed by GitHub
parent 119bfdf9b9
commit d5f109ac4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 80 deletions

View File

@ -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));

View File

@ -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(

View File

@ -66,7 +66,7 @@ const initialState = {
...defaultFetchState
},
allChallengesInfo: {
challengeEdges: [],
challengeNodes: [],
certificateNodes: []
},
userProfileFetchState: {

View File

@ -243,7 +243,7 @@ export type CertificateNode = {
};
export type AllChallengesInfo = {
challengeEdges: { node: ChallengeNode }[];
challengeNodes: ChallengeNode[];
certificateNodes: CertificateNode[];
};

View File

@ -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
})

View File

@ -19,7 +19,7 @@ describe('completionEpic', () => {
challenge: { challengeMeta: { challengeType: 0 } },
app: {
user: { username: 'test' },
allChallengesInfo: { challengeEdges: [], certificateNodes: [] }
allChallengesInfo: { challengeNodes: [], certificateNodes: [] }
}
}
};

View File

@ -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