Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
"categories": {
"correctness": "warn"
},
"plugins": ["react"],
"rules": {
"eslint/no-unused-vars": "off",
"typescript/unbound-method": "off", // 🛑 TEMPORARY
"eslint/no-unused-vars": ["warn", {
// Allow using {ignoredProp, ...keepTheRest} to omit a prop like 'ignoredProp' from an object.
"ignoreRestSiblings": true,
}],
// We disable exhaustive-deps because: it's noisy, and we often include extra deps when we want a memoized thing to
// re-calculate after some change, even if we're not using that thing in the calculation.
"react-hooks/exhaustive-deps": "off",
// Rule of hooks is useful, but not on by default:
"react/rules-of-hooks": "warn",
"typescript/no-floating-promises": ["error", {
"allowForKnownSafeCalls": [
// queryClient.invalidateQueries returns a promise that can be awaited
Expand Down
2 changes: 1 addition & 1 deletion plugins/course-apps/teams/GroupEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const GroupEditor = ({
? (
<div className="d-flex flex-column card rounded mb-3 px-4 py-2 p-4" key="isDeleting">
<h4 className="mb-3">{intl.formatMessage(messages.groupDeleteHeading)}</h4>
{intl.formatMessage(messages.groupDeleteBody).split('\n').map(text => <p>{text}</p>)}
{intl.formatMessage(messages.groupDeleteBody).split('\n').map(text => <p key={text}>{text}</p>)}
<div className="d-flex flex-row justify-content-end">
<Button variant="muted" size="sm" onClick={cancelDeletion}>
{intl.formatMessage(messages.cancel)}
Expand Down
2 changes: 1 addition & 1 deletion src/advanced-settings/AdvancedSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ const AdvancedSettings = () => {
role="dialog"
actions={[
!isQueryPending ? (
<Button variant="tertiary" onClick={handleResetSettingsValues}>
<Button key="cancelBtn" variant="tertiary" onClick={handleResetSettingsValues}>
{intl.formatMessage(messages.buttonCancelText)}
</Button>
) : /* istanbul ignore next */ null,
Expand Down
4 changes: 1 addition & 3 deletions src/course-libraries/OutOfSyncAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ export const OutOfSyncAlert: React.FC<OutOfSyncAlertProps> = ({
variant="info"
onClose={dismissAlert}
actions={[
<Button
onClick={onReview}
>
<Button key="review-btn" onClick={onReview}>
{intl.formatMessage(messages.outOfSyncCountAlertReviewBtn)}
</Button>,
]}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Tab,
Expand Down
3 changes: 3 additions & 0 deletions src/course-outline/page-alerts/PageAlerts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const PageAlerts = ({
onClose={onDismiss}
actions={[
<Button
key="learnMore"
href={discussionsIncontextLearnmoreUrl}
target="_blank"
>
Expand Down Expand Up @@ -267,6 +268,7 @@ const PageAlerts = ({
onClose={onDismiss}
actions={[
<Button
key="view-files"
as={Link}
to={getAssetsUrl()}
>
Expand Down Expand Up @@ -329,6 +331,7 @@ const PageAlerts = ({
onClose={onDismiss}
actions={[
<Button
key="view-files"
as={Link}
to={getAssetsUrl()}
>
Expand Down
8 changes: 3 additions & 5 deletions src/course-unit/CourseUnit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,12 @@ const CourseUnit = () => {
</TransitionReplace>
{courseUnit.upstreamInfo?.upstreamLink && (
<AlertMessage
title={intl.formatMessage(
description={intl.formatMessage(
messages.alertLibraryUnitReadOnlyText,
{
link: (
<Alert.Link
href={courseUnit.upstreamInfo.upstreamLink}
>
{intl.formatMessage(messages.alertLibraryUnitReadOnlyLinkText)}
<Alert.Link href={courseUnit.upstreamInfo.upstreamLink}>
<FormattedMessage {...messages.alertLibraryUnitReadOnlyLinkText} />
</Alert.Link>
),
},
Expand Down
2 changes: 2 additions & 0 deletions src/course-unit/clipboard/paste-notification/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const PastNotificationAlert = ({ staticFileNotices, courseId }) => {
icon={WarningIcon}
dismissible
actions={[
// oxlint-disable-next-line react/jsx-key (Paragon <Alert> adds its own key for action buttons)
<ActionButton
courseId={courseId}
title={intl.formatMessage(messages.hasConflictingErrorsButtonText)}
Expand Down Expand Up @@ -87,6 +88,7 @@ const PastNotificationAlert = ({ staticFileNotices, courseId }) => {
icon={InfoIcon}
dismissible
actions={[
// oxlint-disable-next-line react/jsx-key (Paragon <Alert> adds its own key for action buttons)
<ActionButton
courseId={courseId}
title={intl.formatMessage(messages.hasNewFilesButtonText)}
Expand Down
2 changes: 2 additions & 0 deletions src/course-unit/unit-sidebar/AddSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ const AddNewContent = () => {
<Stack gap={2}>
{templatesByType.advanced?.templates.map((advancedTypeObj) => (
<BlockCardButton
key={advancedTypeObj.category}
blockType={advancedTypeObj.category}
name={advancedTypeObj.displayName}
onClick={() => handleSelection('advanced', advancedTypeObj.category)}
Expand All @@ -223,6 +224,7 @@ const AddNewContent = () => {
{blockTypes.map((blockTypeObj) => (
<BlockCardButton
{...blockTypeObj}
key={blockTypeObj.blockType}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was resulting in a React console warning, and eslint was not picking it up but oxlint now catches it.

installHook.js:1 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `AddNewContent`. See https://reactjs.org/link/warning-keys for more information.
    at BlockCardButton (webpack-internal:///./src/generic/sidebar/BlockCardButton.tsx:26:3)
    at AddNewContent (webpack-internal:///./src/course-unit/unit-sidebar/AddSidebar.tsx:90:88)

templates={templatesByType[blockTypeObj.blockType].templates}
onClick={() => handleSelection(blockTypeObj.blockType)}
onClickTemplate={(boilerplateName: string) => handleSelection(blockTypeObj.blockType, boilerplateName)}
Expand Down
2 changes: 2 additions & 0 deletions src/course-updates/CourseUpdates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ const CourseUpdates = () => {
{courseUpdates.map((courseUpdate, index) => (
isInnerFormOpen(courseUpdate.id) ? (
<UpdateForm
key={courseUpdate.id}
close={closeUpdateForm}
requestType={requestType}
isInnerForm
Expand All @@ -182,6 +183,7 @@ const CourseUpdates = () => {
/>
) : (
<CourseUpdate
key={courseUpdate.id}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was resulting in a React console warning, and eslint was not picking it up but oxlint now catches it.

Warning: Each child in a list should have a unique "key" prop.
Check the render method of `CourseUpdates`. See https://reactjs.org/link/warning-keys for more information.
    at CourseUpdate (webpack-internal:///./src/course-updates/course-update/CourseUpdate.jsx:32:3)
    at CourseUpdates (webpack-internal:///./src/course-updates/CourseUpdates.tsx:69:87)

dateForUpdate={courseUpdate.date}
contentForUpdate={courseUpdate.content}
onEdit={() => handleOpenUpdateForm(REQUEST_TYPES.edit_update, courseUpdate)}
Expand Down
1 change: 1 addition & 0 deletions src/editors/EditorContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const EditorContainer: React.FC<Props> = ({
description={intl.formatMessage(messages.libraryBlockEditWarningDescription)}
actions={[
<Button
key="edit-warning"
destination={getLibraryBlockUrl()}
target="_blank"
rel="noopener noreferrer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const GroupFeedbackRow = ({
{answers.map((letter) => (
<Form.Checkbox
className="mr-4 mt-1"
key={letter.id}
value={letter.id}
checked={value.answers.indexOf(letter.id)}
isValid={value.answers.indexOf(letter.id) >= 0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ const LicenseSelector = ({
onChange={(e) => onLicenseChange(e.target.value)}
>
{Object.entries(LicenseNames).map(([key, text]) => {
if (license === key) { return (<option value={LicenseTypes[key]} selected>{text}</option>); }
if (key === LicenseTypes.select) { return (<option hidden>{text}</option>); }
return (<option value={LicenseTypes[key]}>{text}</option>);
if (license === key) { return (<option key={key} value={LicenseTypes[key]} selected>{text}</option>); }
if (key === LicenseTypes.select) { return (<option key={key} hidden>{text}</option>); }
return (<option key={key} value={LicenseTypes[key]}>{text}</option>);
})}
</Form.Control>
{level !== LicenseLevel.course ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,14 @@ const LanguageSelector = ({
<Dropdown.Menu>
{Object.entries(videoTranscriptLanguages).map(([lang, text]) => {
if (language === lang) {
return (<Dropdown.Item>{text}<Icon className="text-primary-500" src={Check} /></Dropdown.Item>);
return <Dropdown.Item key={lang}>{text}<Icon className="text-primary-500" src={Check} /></Dropdown.Item>;
}
if (openLanguages.some(row => row.includes(lang))) {
return (<Dropdown.Item onClick={() => onLanguageChange({ newLang: lang })}>{text}</Dropdown.Item>);
return (
<Dropdown.Item key={lang} onClick={() => onLanguageChange({ newLang: lang })}>{text}</Dropdown.Item>
);
}
return (<Dropdown.Item className="disabled">{text}</Dropdown.Item>);
return (<Dropdown.Item key={lang} className="disabled">{text}</Dropdown.Item>);
})}
</Dropdown.Menu>
</Dropdown>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ const TranscriptWidget = ({
<Form.Group className="border-primary-100 border-bottom">
{transcripts.map((language, index) => (
<Transcript
key={language}
language={language}
transcriptUrl={selectedVideoTranscriptUrls[language]}
index={index}
Expand Down
2 changes: 1 addition & 1 deletion src/files-and-videos/files-page/FileValidationModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const FileValidationModal = ({
<ModalDialog.Body>
<FormattedMessage {...messages.overwriteConfirmMessage} />
<ul className="mt-2">
{Object.keys(duplicateFiles).map(file => <li>{file}</li>)}
{Object.keys(duplicateFiles).map(file => <li key={file}>{file}</li>)}
</ul>
</ModalDialog.Body>
<ModalDialog.Footer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ const SortAndFilterModal = ({
isInline
>
{filterOptions.map(({ name, value }) => (
<Form.Checkbox {...{ value, key: value }}>{name}</Form.Checkbox>
<Form.Checkbox key={value} value={value}>{name}</Form.Checkbox>
))}
</Form.CheckboxSet>
</Form.Group>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ const TranscriptTab = ({
))}
</ul>
</ErrorAlert>
{previousSelection.map(transcript => (
{previousSelection.map((transcript, idx) => (
<Transcript
// eslint-disable-next-line react/no-array-index-key
key={idx}
{...{
languages,
transcript,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useIntl } from '@edx/frontend-platform/i18n';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Alert, Button, Hyperlink } from '@openedx/paragon';
import { Policy } from '@openedx/paragon/icons';
import { AgreementGated } from '@src/constants';
Expand All @@ -11,7 +11,6 @@ import {
import messages from './messages';

const AlertAgreement = ({ agreementType }: { agreementType: string }) => {
const intl = useIntl();
const { data, isLoading, isError } = useUserAgreement(agreementType);
const mutation = useUserAgreementRecordUpdater(agreementType);
const showAlert = data && !isLoading && !isError;
Expand All @@ -30,8 +29,8 @@ const AlertAgreement = ({ agreementType }: { agreementType: string }) => {
variant="warning"
icon={Policy}
actions={[
<Hyperlink destination={url}>{intl.formatMessage(messages.learnMoreLinkLabel)}</Hyperlink>,
<Button onClick={handleAcceptAgreement}>{intl.formatMessage(messages.agreeButtonLabel)}</Button>,
<Hyperlink key="learn-more" destination={url}><FormattedMessage {...messages.learnMoreLinkLabel} /></Hyperlink>,
<Button key="agree" onClick={handleAcceptAgreement}><FormattedMessage {...messages.agreeButtonLabel} /></Button>,
]}
>
<Alert.Heading>{name}</Alert.Heading>
Expand Down
2 changes: 1 addition & 1 deletion src/generic/sidebar/BlockCardButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const BlockCardButton = ({
>
<Stack direction="horizontal" className="d-flex flex-wrap" gap={2}>
{templates.map((template) => (
<Chip onClick={() => onClickTemplate?.(template.boilerplateName)}>
<Chip onClick={() => onClickTemplate?.(template.boilerplateName)} key={template.boilerplateName}>
{template.displayName}
</Chip>
))}
Expand Down
2 changes: 1 addition & 1 deletion src/generic/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export function Sidebar<T extends SidebarPages>({
}

return (
<IconButton {...buttonData} />
<IconButton key={key} {...buttonData} />
);
})}
</IconButtonToggle>
Expand Down
2 changes: 1 addition & 1 deletion src/grading-settings/GradingSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ const GradingSettings = () => {
role="dialog"
actions={[
!isQueryPending && (
<Button variant="tertiary" onClick={handleResetPageData}>
<Button key="cancel" variant="tertiary" onClick={handleResetPageData}>
{intl.formatMessage(messages.buttonCancelText)}
</Button>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import EnrollmentTrackGroupsSection from './enrollment-track-groups-section';
import GroupConfigurationSidebar from './group-configuration-sidebar';
import { useGroupConfigurations } from './hooks';
import ConnectionErrorAlert from '../generic/ConnectionErrorAlert';
import { AvailableGroup } from './types';

const GroupConfigurations = () => {
const { formatMessage } = useIntl();
Expand All @@ -39,7 +40,7 @@ const GroupConfigurations = () => {
} = useGroupConfigurations(courseId);

document.title = getPageHeadTitle(
courseDetails?.name,
courseDetails?.name ?? '',
formatMessage(messages.headingTitle),
);

Expand All @@ -59,13 +60,13 @@ const GroupConfigurations = () => {
);
}

const enrollmentTrackGroup = shouldShowEnrollmentTrack
const enrollmentTrackGroup: AvailableGroup = shouldShowEnrollmentTrack
? allGroupConfigurations.find((group) => group.scheme === 'enrollment_track')
: null;

const contentGroup = allGroupConfigurations.find((group) => group.scheme === 'cohort');
const contentGroup: AvailableGroup[] = allGroupConfigurations.find((group) => group.scheme === 'cohort');

const teamGroups = allGroupConfigurations.filter((group) => group.scheme === 'team');
const teamGroups: AvailableGroup[] = allGroupConfigurations.filter((group) => group.scheme === 'team');

return (
<>
Expand All @@ -90,6 +91,7 @@ const GroupConfigurations = () => {
{!!teamGroups && teamGroups.length > 0 && (
teamGroups.map((teamGroup) => (
<TeamGroupsSection
key={teamGroup.id}
availableGroup={teamGroup}
/>
))
Expand Down
1 change: 1 addition & 0 deletions src/library-authoring/LibraryContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const LibraryContent = ({ contentType = ContentType.home }: LibraryContentProps)
})}
{showPlaceholderBlocks && placeholderData?.hits?.map((item) => (
<PlaceholderCard
key={item.usage_key}
displayName={item.display_name}
blockType={item.block_type}
/>
Expand Down
5 changes: 4 additions & 1 deletion src/library-authoring/import-course/ImportDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ const ImportDetailsContent = () => {
});
navigate(`../import/${courseImportDetails.source}/${newMigrationTask.uuid}`);
setDisableReimport(false);
} catch (error) {
} catch {
showToast(intl.formatMessage(messages.importCourseCompleteFailedToastMessage, {
courseName: courseDetails.title,
}));
Expand All @@ -234,6 +234,7 @@ const ImportDetailsContent = () => {
stacked
actions={[
<Button
key="view-content"
variant="outline-primary"
iconAfter={ArrowForward}
onClick={() => navigate(collectionLink())}
Expand Down Expand Up @@ -289,6 +290,7 @@ const ImportDetailsContent = () => {
stacked
actions={[
<Button
key="retry-btn"
variant="outline-primary"
iconAfter={ArrowForward}
onClick={handleImportCourse}
Expand Down Expand Up @@ -330,6 +332,7 @@ const ImportDetailsContent = () => {
stacked
actions={[
<Button
key="view-content"
variant="outline-primary"
iconAfter={ArrowForward}
onClick={() => navigate(collectionLink())}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const ImportStepperPage = () => {
compositionLevel: 'section',
});
navigate(`../import/${selectedCourseId}/${migrationTask.uuid}`);
} catch (error) {
} catch {
showToast(intl.formatMessage(messages.importCourseCompleteFailedToastMessage, {
courseName: courseData?.title,
}));
Expand Down
6 changes: 3 additions & 3 deletions src/optimizer-page/scan-results/ScanResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { useDispatch } from 'react-redux';
import messages from './messages';
import SectionCollapsible from './SectionCollapsible';
import BrokenLinkTable from './BrokenLinkTable';
import { LinkCheckResult } from '../types';
import type { LinkCheckResult, Section } from '../types';
import { countBrokenLinks, isDataEmpty } from '../utils';
import FilterModal from './filterModal';
import { useWaffleFlags } from '../../data/apiHooks';
Expand Down Expand Up @@ -131,7 +131,7 @@ const ScanResults: FC<Props> = ({
}, [data?.courseUpdates, data?.customPages, intl]);

// Combine renderable sections with regular sections
const allSections = useMemo(
const allSections: Section[] = useMemo(
() => [...renderableSections, ...(sections || [])],
[renderableSections, sections],
);
Expand Down Expand Up @@ -1087,7 +1087,7 @@ const ScanResults: FC<Props> = ({
{section.subsections.map((subsection) => (
<>
{subsection.units.map((unit) => (
<div className="unit">
<div className="unit" key={unit.id}>
<BrokenLinkTable
unit={unit}
linkType="previous"
Expand Down
Loading