From fa9fb61f6a0db3ceedc84dacb97784b4cfbf0653 Mon Sep 17 00:00:00 2001 From: awu43 <46470763+awu43@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:47:47 -0700 Subject: [PATCH] feat(client): convert class components to functional components (#43226) --- client/src/client-only-routes/show-user.tsx | 171 +++++----- client/src/components/layouts/learn.tsx | 74 +++-- .../profile/components/TimeLine.tsx | 263 +++++++--------- .../src/components/settings/danger-zone.tsx | 129 ++++---- client/src/components/settings/email.tsx | 295 ++++++++---------- client/src/components/settings/privacy.tsx | 275 ++++++++-------- client/src/html.tsx | 57 ++-- .../Challenges/components/output.tsx | 31 +- .../Challenges/components/prism-formatted.tsx | 38 +-- .../Challenges/projects/tool-panel.tsx | 46 +-- 10 files changed, 632 insertions(+), 747 deletions(-) diff --git a/client/src/client-only-routes/show-user.tsx b/client/src/client-only-routes/show-user.tsx index ec3bc6b798..f2cac138a6 100644 --- a/client/src/client-only-routes/show-user.tsx +++ b/client/src/client-only-routes/show-user.tsx @@ -7,7 +7,7 @@ import { Col, Row } from '@freecodecamp/react-bootstrap'; -import React, { Component } from 'react'; +import React, { useState } from 'react'; import Helmet from 'react-helmet'; import { TFunction, Trans, withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; @@ -58,111 +58,94 @@ const mapDispatchToProps = { reportUser }; -class ShowUser extends Component { - state: { - textarea: string; - }; - constructor(props: IShowUserProps) { - super(props); +function ShowUser({ + email, + isSignedIn, + reportUser, + t, + userFetchState, + username +}: IShowUserProps) { + const [textarea, setTextarea] = useState(''); - this.state = { - textarea: '' - }; - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + function handleChange(e: React.ChangeEvent) { + setTextarea(e.target.value.slice()); } - handleChange(e: React.ChangeEvent) { - const textarea = e.target.value.slice(); - return this.setState({ - textarea - }); - } - - handleSubmit(e: React.FormEvent) { + function handleSubmit(e: React.FormEvent) { e.preventDefault(); - const { textarea: reportDescription } = this.state; - const { username, reportUser } = this.props; - return reportUser({ username, reportDescription }); + reportUser({ username, reportDescription: textarea }); } - render() { - const { username, isSignedIn, userFetchState, email, t } = this.props; - const { pending, complete, errored } = userFetchState; - if (pending && !complete) { - return ; - } + const { pending, complete, errored } = userFetchState; + if (pending && !complete) { + return ; + } - if ((complete || errored) && !isSignedIn) { - return ( -
- - - - - - {t('report.sign-in')} - - - - - - {t('buttons.click-here')} - - - - - -
- ); - } - - const { textarea } = this.state; - const placeholderText = t('report.details'); + if ((complete || errored) && !isSignedIn) { return ( - <> - - {t('report.portfolio')} | freeCodeCamp.org - - - - -

{t('report.portfolio-2', { username: username })}

- -
- - -

- - {{ email }} - -

-

{t('report.notify-2')}

- {/* eslint-disable @typescript-eslint/unbound-method */} -
- - {t('report.what')} - - - - - - {/* eslint-disable @typescript-eslint/unbound-method */} - -
- +
+ + + + + + {t('report.sign-in')} + + + + + + {t('buttons.click-here')} + + + + + +
); } + + return ( + <> + + {t('report.portfolio')} | freeCodeCamp.org + + + + +

{t('report.portfolio-2', { username: username })}

+ +
+ + +

+ + {{ email }} + +

+

{t('report.notify-2')}

+
+ + {t('report.what')} + + + + + + +
+ + ); } -// @ts-expect-error Config might need to be remedied, or component transformed into F.C. ShowUser.displayName = 'ShowUser'; export default withTranslation()( diff --git a/client/src/components/layouts/learn.tsx b/client/src/components/layouts/learn.tsx index 9df35f09a7..525048569c 100644 --- a/client/src/components/layouts/learn.tsx +++ b/client/src/components/layouts/learn.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useEffect } from 'react'; import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; @@ -52,48 +52,46 @@ type LearnLayoutProps = { children?: React.ReactNode; }; -class LearnLayout extends Component { - static displayName = 'LearnLayout'; +function LearnLayout({ + isSignedIn, + fetchState, + user, + tryToShowDonationModal, + children +}: LearnLayoutProps): JSX.Element { + useEffect(() => { + tryToShowDonationModal(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - componentDidMount() { - this.props.tryToShowDonationModal(); + useEffect(() => { + return () => { + const metaTag = document.querySelector(`meta[name="robots"]`); + if (metaTag) { + metaTag.remove(); + } + }; + }, []); + + if (fetchState.pending && !fetchState.complete) { + return ; } - componentWillUnmount() { - const metaTag = document.querySelector(`meta[name="robots"]`); - if (metaTag) { - metaTag.remove(); - } + if (isSignedIn && !user.acceptedPrivacyTerms) { + return ; } - render() { - const { - fetchState: { pending, complete }, - isSignedIn, - user: { acceptedPrivacyTerms }, - children - } = this.props; - - if (pending && !complete) { - return ; - } - - if (isSignedIn && !acceptedPrivacyTerms) { - return ; - } - - return ( - <> - - - -
{children}
- {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ - /* @ts-ignore */} - - - ); - } + return ( + <> + + + +
{children}
+ {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ + /* @ts-ignore */} + + + ); } export default connect(mapStateToProps, mapDispatchToProps)(LearnLayout); diff --git a/client/src/components/profile/components/TimeLine.tsx b/client/src/components/profile/components/TimeLine.tsx index 64f4364b0f..4cccc6889b 100644 --- a/client/src/components/profile/components/TimeLine.tsx +++ b/client/src/components/profile/components/TimeLine.tsx @@ -9,7 +9,7 @@ import { import Loadable from '@loadable/component'; import { useStaticQuery, graphql } from 'gatsby'; import { reverse, sortBy } from 'lodash-es'; -import React, { Component, useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { TFunction, withTranslation } from 'react-i18next'; import envData from '../../../../../config/env.json'; @@ -66,43 +66,58 @@ interface TimelineInnerProps extends TimelineProps { totalPages: number; } -interface TimeLineInnerState { - solutionToView: string | null; - solutionOpen: boolean; - pageNo: number; - solution: string | null; - challengeFiles: ChallengeFiles; -} +function TimelineInner({ + idToNameMap, + sortedTimeline, + totalPages, -class TimelineInner extends Component { - constructor(props: TimelineInnerProps) { - super(props); + completedMap, + t, + username +}: TimelineInnerProps) { + const [solutionToView, setSolutionToView] = useState(null); + const [solutionOpen, setSolutionOpen] = useState(false); + const [pageNo, setPageNo] = useState(1); + const [solution, setSolution] = useState(null); + const [challengeFiles, setChallengeFiles] = useState(null); - this.state = { - solutionToView: null, - solutionOpen: false, - pageNo: 1, - solution: null, - challengeFiles: null - }; - - this.closeSolution = this.closeSolution.bind(this); - this.renderCompletion = this.renderCompletion.bind(this); - this.viewSolution = this.viewSolution.bind(this); - this.firstPage = this.firstPage.bind(this); - this.prevPage = this.prevPage.bind(this); - this.nextPage = this.nextPage.bind(this); - this.lastPage = this.lastPage.bind(this); - this.renderViewButton = this.renderViewButton.bind(this); + function viewSolution( + id: string, + solution_: string, + challengeFiles_: ChallengeFiles + ): void { + setSolutionToView(id); + setSolutionOpen(true); + setSolution(solution_); + setChallengeFiles(challengeFiles_); } - renderViewButton( + function closeSolution(): void { + setSolutionToView(null); + setSolutionOpen(false); + setSolution(null); + setChallengeFiles(null); + } + + function firstPage(): void { + setPageNo(1); + } + function nextPage(): void { + setPageNo(prev => prev + 1); + } + function prevPage(): void { + setPageNo(prev => prev - 1); + } + function lastPage(): void { + setPageNo(totalPages); + } + + function renderViewButton( id: string, challengeFiles: ChallengeFiles, githubLink: string, solution: string ): React.ReactNode { - const { t } = this.props; if (challengeFiles?.length) { return ( @@ -163,8 +178,7 @@ class TimelineInner extends Component { } } - renderCompletion(completed: SortedTimeline): JSX.Element { - const { idToNameMap, username } = this.props; + function renderCompletion(completed: SortedTimeline): JSX.Element { const { id, challengeFiles, githubLink, solution } = completed; const completedDate = new Date(completed.completedDate); // @ts-expect-error idToNameMap is not a Map... @@ -184,9 +198,7 @@ class TimelineInner extends Component { {challengeTitle} )} - - {this.renderViewButton(id, challengeFiles, githubLink, solution)} - + {renderViewButton(id, challengeFiles, githubLink, solution)}