Skip to content

Commit 88d5b41

Browse files
authored
Migrate to useDispatch and useSelector (#2759)
* Rename Dashboard component to match filename The component should be called `AchievementDashboard` instead. * Use `useDispatch` in achievement dashboard * Use `useSelector` for achievement dashboard * Remove unused props and components * Update typing of AchievementControl component * Refactor AchievementControl * Remove extra spaces * Destructure props directly in argument * Use `useDispatch` in AchievementControl * Use `useSelector` in AchievementControl * Remove unused types and components * Use `useDispatch` in GradingEditor * Remove unused types and components Also refactored typings for dispatch handlers for readability. * Refactor `DefaultChapterSelect` Improves typing and consistency with the rest of the codebase. * Use `useDispatch` in DefaultChapterSelect * Use `useSession` in DefaultChapterSelect It encompasses all the selector needs of the component. * Remove unused types and components * Fix incorrect `sourceChapter` type in app state * Refactor `MissionCreator` Improves typing and consistency with the rest of the codebase. * Use `useDispatch` in MissionCreator * Remove console log statements * Remove unused types and components
1 parent 27aa481 commit 88d5b41

File tree

15 files changed

+178
-303
lines changed

15 files changed

+178
-303
lines changed

src/commons/application/types/SessionTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export type SessionState = {
104104
readonly enableAchievements?: boolean;
105105
readonly enableSourcecast?: boolean;
106106
readonly enableStories?: boolean;
107-
readonly sourceChapter?: number;
107+
readonly sourceChapter?: Chapter;
108108
readonly sourceVariant?: Variant;
109109
readonly moduleHelpText?: string;
110110
readonly assetsPrefix?: string;

src/commons/missionCreator/MissionCreator.tsx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { FileInput } from '@blueprintjs/core';
22
import { IconNames } from '@blueprintjs/icons';
3-
import * as React from 'react';
3+
import React, { useCallback, useEffect, useState } from 'react';
4+
import { useDispatch } from 'react-redux';
45
import { parseString } from 'xml2js';
56

7+
import { updateAssessment } from '../application/actions/SessionActions';
68
import {
79
Assessment,
810
AssessmentOverview,
@@ -17,27 +19,26 @@ import {
1719
storeLocalAssessmentOverview
1820
} from '../XMLParser/XMLParserHelper';
1921

20-
type MissionCreatorProps = DispatchProps & OwnProps;
21-
22-
export type DispatchProps = {
23-
newAssessment: (assessment: Assessment) => void;
24-
};
25-
26-
type OwnProps = {
22+
type Props = {
2723
updateEditingOverview: (overview: AssessmentOverview) => void;
2824
};
2925

30-
function MissionCreator(props: MissionCreatorProps) {
31-
const [fileInputText, setFileInputText] = React.useState('Import XML');
26+
const MissionCreator: React.FC<Props> = props => {
27+
const [fileInputText, setFileInputText] = useState('Import XML');
3228
let fileReader: FileReader | undefined = undefined;
3329

34-
React.useEffect(() => {
30+
const dispatch = useDispatch();
31+
const newAssessment = useCallback(
32+
(assessment: Assessment) => dispatch(updateAssessment(assessment)),
33+
[dispatch]
34+
);
35+
36+
useEffect(() => {
3537
const assessment = retrieveLocalAssessment();
3638
if (assessment) {
37-
props.newAssessment(assessment);
39+
newAssessment(assessment);
3840
}
39-
// eslint-disable-next-line react-hooks/exhaustive-deps
40-
}, [props.newAssessment]);
41+
}, [newAssessment]);
4142

4243
const handleFileRead = (file: any) => (e: any) => {
4344
if (!fileReader) {
@@ -46,18 +47,16 @@ function MissionCreator(props: MissionCreatorProps) {
4647
const content = fileReader.result;
4748
if (content) {
4849
parseString(content, (err: any, result: any) => {
49-
console.dir(file);
5050
try {
5151
const entireAssessment: [AssessmentOverview, Assessment] = makeEntireAssessment(result);
5252
entireAssessment[0].fileName = file.name.slice(0, -4);
5353
storeLocalAssessmentOverview(entireAssessment[0]);
5454
props.updateEditingOverview(entireAssessment[0]);
5555

5656
storeLocalAssessment(entireAssessment[1]);
57-
props.newAssessment(entireAssessment[1]);
57+
newAssessment(entireAssessment[1]);
5858
setFileInputText('Success!');
5959
} catch (err) {
60-
console.log(err);
6160
setFileInputText('Invalid XML!');
6261
}
6362
});
@@ -77,7 +76,7 @@ function MissionCreator(props: MissionCreatorProps) {
7776
storeLocalAssessmentOverview(overviewTemplate());
7877
props.updateEditingOverview(overviewTemplate());
7978
storeLocalAssessment(assessmentTemplate());
80-
props.newAssessment(assessmentTemplate());
79+
newAssessment(assessmentTemplate());
8180
};
8281

8382
return (
@@ -95,6 +94,6 @@ function MissionCreator(props: MissionCreatorProps) {
9594
</div>
9695
</div>
9796
);
98-
}
97+
};
9998

10099
export default MissionCreator;

src/commons/missionCreator/MissionCreatorContainer.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/pages/academy/grading/subcomponents/GradingEditor.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ import {
1010
Pre
1111
} from '@blueprintjs/core';
1212
import { IconNames } from '@blueprintjs/icons';
13-
import React, { useEffect, useState } from 'react';
13+
import React, { useEffect, useMemo, useState } from 'react';
1414
import ReactMde, { ReactMdeProps } from 'react-mde';
15+
import { useDispatch } from 'react-redux';
1516

17+
import {
18+
reautogradeAnswer,
19+
submitGrading,
20+
submitGradingAndContinue
21+
} from '../../../../commons/application/actions/SessionActions';
1622
import ControlButton from '../../../../commons/ControlButton';
1723
import Markdown from '../../../../commons/Markdown';
1824
import { Prompt } from '../../../../commons/ReactRouterPrompt';
@@ -24,22 +30,14 @@ import {
2430
} from '../../../../commons/utils/notifications/NotificationsHelper';
2531
import { convertParamToInt } from '../../../../commons/utils/ParamParseHelper';
2632

27-
type GradingEditorProps = DispatchProps & OwnProps;
28-
2933
type GradingSaveFunction = (
3034
submissionId: number,
3135
questionId: number,
3236
xpAdjustment: number | undefined,
3337
comments?: string
3438
) => void;
3539

36-
export type DispatchProps = {
37-
handleGradingSave: GradingSaveFunction;
38-
handleGradingSaveAndContinue: GradingSaveFunction;
39-
handleReautogradeAnswer: (submissionId: number, questionId: number) => void;
40-
};
41-
42-
type OwnProps = {
40+
type Props = {
4341
solution: number | string | null;
4442
questionId: number;
4543
submissionId: number;
@@ -55,7 +53,22 @@ type OwnProps = {
5553

5654
const gradingEditorButtonClass = 'grading-editor-button';
5755

58-
const GradingEditor: React.FC<GradingEditorProps> = props => {
56+
const GradingEditor: React.FC<Props> = props => {
57+
const dispatch = useDispatch();
58+
const { handleGradingSave, handleGradingSaveAndContinue, handleReautogradeAnswer } = useMemo(
59+
() =>
60+
({
61+
handleGradingSave: (...args) => dispatch(submitGrading(...args)),
62+
handleGradingSaveAndContinue: (...args) => dispatch(submitGradingAndContinue(...args)),
63+
handleReautogradeAnswer: (...args) => dispatch(reautogradeAnswer(...args))
64+
}) satisfies {
65+
handleGradingSave: GradingSaveFunction;
66+
handleGradingSaveAndContinue: GradingSaveFunction;
67+
handleReautogradeAnswer: (submissionId: number, questionId: number) => void;
68+
},
69+
[dispatch]
70+
);
71+
5972
/**
6073
* A potentially null string which defines the
6174
* result for the number XP input. This property being null
@@ -140,7 +153,7 @@ const GradingEditor: React.FC<GradingEditorProps> = props => {
140153
comments?: string
141154
) => {
142155
const callback = (): void => {
143-
props.handleGradingSaveAndContinue(submissionId, questionId, xpAdjustment, comments!);
156+
handleGradingSaveAndContinue(submissionId, questionId, xpAdjustment, comments!);
144157
};
145158
setCurrentlySaving(true);
146159
// TODO: Check (not sure how) if this results in a regression.
@@ -159,7 +172,7 @@ const GradingEditor: React.FC<GradingEditorProps> = props => {
159172
positiveIntent: 'danger'
160173
});
161174
if (confirm) {
162-
props.handleReautogradeAnswer(props.submissionId, props.questionId);
175+
handleReautogradeAnswer(props.submissionId, props.questionId);
163176
}
164177
};
165178

@@ -300,7 +313,7 @@ const GradingEditor: React.FC<GradingEditorProps> = props => {
300313
<ControlButton
301314
label="Save Changes"
302315
icon={IconNames.FLOPPY_DISK}
303-
onClick={validateXpBeforeSave(props.handleGradingSave)}
316+
onClick={validateXpBeforeSave(handleGradingSave)}
304317
options={saveButtonOpts}
305318
/>
306319
</div>

src/pages/academy/grading/subcomponents/GradingEditorContainer.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/pages/academy/grading/subcomponents/GradingWorkspace.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { SideContentTab, SideContentType } from '../../../../commons/sideContent
5757
import Workspace, { WorkspaceProps } from '../../../../commons/workspace/Workspace';
5858
import { WorkspaceLocation, WorkspaceState } from '../../../../commons/workspace/WorkspaceTypes';
5959
import { AnsweredQuestion } from '../../../../features/grading/GradingTypes';
60-
import GradingEditor from './GradingEditorContainer';
60+
import GradingEditor from './GradingEditor';
6161

6262
type GradingWorkspaceProps = {
6363
submissionId: number;

src/pages/academy/groundControl/GroundControl.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
AssessmentOverview
1313
} from '../../../commons/assessment/AssessmentTypes';
1414
import ContentDisplay from '../../../commons/ContentDisplay';
15-
import DefaultChapterSelect from './subcomponents/DefaultChapterSelectContainer';
15+
import DefaultChapterSelect from './subcomponents/DefaultChapterSelect';
1616
import DeleteCell from './subcomponents/GroundControlDeleteCell';
1717
import Dropzone from './subcomponents/GroundControlDropzone';
1818
import EditCell from './subcomponents/GroundControlEditCell';

src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,59 @@
11
import { Button, Classes, Dialog, Intent, Menu, MenuItem } from '@blueprintjs/core';
22
import { IconNames } from '@blueprintjs/icons';
33
import { ItemListRenderer, ItemRenderer, Select } from '@blueprintjs/select';
4-
import { Chapter, Variant } from 'js-slang/dist/types';
5-
import * as React from 'react';
4+
import { Variant } from 'js-slang/dist/types';
5+
import React, { useCallback, useState } from 'react';
6+
import { useDispatch } from 'react-redux';
7+
import Constants from 'src/commons/utils/Constants';
8+
import { useSession } from 'src/commons/utils/Hooks';
69

710
import {
811
SALanguage,
912
sourceLanguages,
1013
styliseSublanguage
1114
} from '../../../../commons/application/ApplicationTypes';
1215
import ControlButton from '../../../../commons/ControlButton';
16+
import { changeSublanguage } from '../../../../commons/workspace/WorkspaceActions';
1317

14-
export type DefaultChapterSelectProps = DispatchProps & StateProps;
18+
const DefaultChapterSelect: React.FC = () => {
19+
const [chosenSublang, setSublanguage] = useState<SALanguage>(sourceLanguages[0]);
20+
const [isDialogOpen, setDialogState] = useState(false);
1521

16-
export type DispatchProps = {
17-
handleUpdateSublanguage: (sublang: SALanguage) => void;
18-
};
19-
20-
export type StateProps = {
21-
sourceChapter: Chapter;
22-
sourceVariant: Variant;
23-
};
22+
const {
23+
// Temporarily load the defaults when the course configuration fetch has yet to return
24+
sourceChapter = Constants.defaultSourceChapter,
25+
sourceVariant = Constants.defaultSourceVariant
26+
} = useSession();
2427

25-
const DefaultChapterSelect: React.FunctionComponent<DefaultChapterSelectProps> = props => {
26-
const { handleUpdateSublanguage } = props;
27-
const { sourceChapter, sourceVariant } = props;
28-
29-
const [chosenSublang, setSublanguage] = React.useState<SALanguage>(sourceLanguages[0]);
30-
const [isDialogOpen, setDialogState] = React.useState<boolean>(false);
28+
const dispatch = useDispatch();
29+
const handleUpdateSublanguage = useCallback(
30+
(sublang: SALanguage) => dispatch(changeSublanguage(sublang)),
31+
[dispatch]
32+
);
3133

32-
const handleOpenDialog = React.useCallback(
34+
const handleOpenDialog = useCallback(
3335
(choice: SALanguage) => {
3436
setDialogState(true);
3537
setSublanguage(choice);
3638
},
3739
[setDialogState, setSublanguage]
3840
);
39-
const handleCloseDialog = React.useCallback(() => {
41+
const handleCloseDialog = useCallback(() => {
4042
setDialogState(false);
4143
}, [setDialogState]);
42-
const handleConfirmDialog = React.useCallback(() => {
44+
const handleConfirmDialog = useCallback(() => {
4345
setDialogState(false);
4446
handleUpdateSublanguage(chosenSublang);
4547
}, [chosenSublang, setDialogState, handleUpdateSublanguage]);
4648

47-
const chapterRenderer: ItemRenderer<SALanguage> = React.useCallback(
49+
const chapterRenderer: ItemRenderer<SALanguage> = useCallback(
4850
(lang, { handleClick }) => (
4951
<MenuItem key={lang.displayName} onClick={handleClick} text={lang.displayName} />
5052
),
5153
[]
5254
);
5355

54-
const chapterListRenderer: ItemListRenderer<SALanguage> = React.useCallback(
56+
const chapterListRenderer: ItemListRenderer<SALanguage> = useCallback(
5557
({ itemsParentRef, renderItem, items }) => {
5658
const defaultChoices = items.filter(({ variant }) => variant === Variant.DEFAULT);
5759
const variantChoices = items.filter(({ variant }) => variant !== Variant.DEFAULT);

src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/pages/achievement/Achievement.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { useTypedSelector } from 'src/commons/utils/Hooks';
44

55
import { Role } from '../../commons/application/ApplicationTypes';
66
import NotFound from '../notFound/NotFound';
7-
import AchievementControl from './control/AchievementControlContainer';
8-
import AchievementDashboard from './subcomponents/AchievementDashboardContainer';
7+
import AchievementControl from './control/AchievementControl';
8+
import AchievementDashboard from './subcomponents/AchievementDashboard';
99

1010
const Achievement: React.FC = () => {
1111
const role = useTypedSelector(state => state.session.role!);

0 commit comments

Comments
 (0)