From 9763247888e2543b7aaba6d9fca61a398f2f6c5c Mon Sep 17 00:00:00 2001 From: Brendan Daoud Date: Tue, 28 Apr 2020 23:02:34 +0200 Subject: [PATCH] Use hooks for redux (#181) * Upgrade react-redux * Lint * Use hooks for projects * Use hook for user * Use hooks for toastr * Remove useless wraps --- frontend/package.json | 6 +- .../components/AccountMenu/AccountMenu.tsx | 7 +- .../AccountMenu/AccountMenu.wrap.tsx | 2 - .../ProjectMenuContent/ProjectMenuContent.tsx | 6 +- .../ProjectMenuContent.wrap.tsx | 2 - frontend/src/pages/Audits/Audits.tsx | 9 +-- frontend/src/pages/Audits/Audits.wrap.tsx | 5 -- .../EnvironmentSettings.tsx | 36 +++------- .../EnvironmentSettings.wrap.tsx | 27 ------- .../src/pages/EnvironmentSettings/index.tsx | 2 +- .../pages/GeneralSettings/GeneralSettings.tsx | 42 ++++------- .../GeneralSettings/GeneralSettings.wrap.tsx | 24 +------ .../pages/MembersSettings/MembersSettings.tsx | 44 +++++------- .../MembersSettings/MembersSettings.wrap.tsx | 20 +----- .../ScriptTable/ScriptModal/ScriptModal.tsx | 14 ++-- .../ScriptModal/ScriptModal.wrap.tsx | 5 +- .../ScriptTable/ScriptRow/ScriptRow.tsx | 15 ++-- .../ScriptTable/ScriptRow/ScriptRow.wrap.tsx | 4 -- .../PagesAndScriptsSettings.tsx | 36 +++------- .../PagesAndScriptsSettings.wrap.tsx | 27 ------- .../pages/PagesAndScriptsSettings/index.tsx | 2 +- frontend/src/pages/Project/Project.tsx | 17 ++--- frontend/src/pages/Project/Project.wrap.tsx | 14 +--- frontend/src/pages/Projects/Projects.tsx | 12 +--- frontend/src/pages/Projects/Projects.wrap.tsx | 19 ----- frontend/src/pages/Projects/index.tsx | 2 +- frontend/src/redux/entities/projects/hooks.ts | 60 ++++++++++++++++ .../projects/useFetchProjectIfUndefined.ts | 14 ---- frontend/src/redux/user/selectors.ts | 5 +- frontend/yarn.lock | 71 ++++++++++--------- 30 files changed, 198 insertions(+), 351 deletions(-) delete mode 100644 frontend/src/pages/EnvironmentSettings/EnvironmentSettings.wrap.tsx delete mode 100644 frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.wrap.tsx delete mode 100644 frontend/src/pages/Projects/Projects.wrap.tsx create mode 100644 frontend/src/redux/entities/projects/hooks.ts delete mode 100644 frontend/src/redux/entities/projects/useFetchProjectIfUndefined.ts diff --git a/frontend/package.json b/frontend/package.json index 2e5b1741d..9ae3bb9a8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "babel-polyfill": "6.26.0", - "connected-react-router": "5.0.1", + "connected-react-router": "^6.0.0", "core-js": "^3.3.3", "dayjs": "^1.8.8", "env-cmd": "8.0.2", @@ -19,7 +19,7 @@ "react-helmet": "^5.2.1", "react-intl": "^3.0.0", "react-modal": "^3.8.1", - "react-redux": "5.1.0", + "react-redux": "^7.0.0", "react-redux-toastr": "^7.5.1", "react-router": "4.3.1", "react-router-dom": "4.3.1", @@ -101,7 +101,7 @@ "@types/react-helmet": "^5.0.15", "@types/react-intl": "^3.0.0", "@types/react-modal": "^3.8.2", - "@types/react-redux": "^6.0.12", + "@types/react-redux": "^7.0.0", "@types/react-redux-toastr": "^7.4.1", "@types/react-router": "^4.4.3", "@types/react-router-dom": "^4.3.1", diff --git a/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.tsx b/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.tsx index 664c5d29d..58ae7e1e3 100644 --- a/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.tsx +++ b/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.tsx @@ -2,7 +2,7 @@ import Loader from 'components/Loader'; import React from 'react'; import { FormattedMessage } from 'react-intl'; import { RouteComponentProps } from 'react-router'; -import { UserState } from 'redux/user/reducer'; +import { useCurrentUser } from 'redux/user/selectors'; import { routeDefinitions } from 'routes'; import { Container, @@ -18,12 +18,13 @@ import { interface OwnProps { logoutUser: (redirectTo?: string | undefined) => void; - user: UserState; } type Props = OwnProps & RouteComponentProps; -export const AccountMenu: React.FunctionComponent = ({ logoutUser, user }) => { +export const AccountMenu: React.FunctionComponent = ({ logoutUser }) => { + const user = useCurrentUser(); + const capitalize = (word: any) => { if (typeof word !== 'string') { return ''; diff --git a/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.wrap.tsx b/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.wrap.tsx index 6f6725ba2..53f38ed77 100644 --- a/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.wrap.tsx +++ b/frontend/src/components/Root/components/Header/components/AccountMenu/AccountMenu.wrap.tsx @@ -3,11 +3,9 @@ import { withRouter } from 'react-router'; import { Dispatch } from 'redux'; import { getIsAuthenticated, logoutUserRequest } from 'redux/login'; import { RootState } from 'redux/types'; -import { getUser } from 'redux/user/selectors'; import { AccountMenu } from './AccountMenu'; const mapStateToProps = (state: RootState) => ({ - user: getUser(state), isUserAuthenticated: getIsAuthenticated(state), }); diff --git a/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.tsx b/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.tsx index df079bb84..07bea7f9f 100644 --- a/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.tsx +++ b/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.tsx @@ -9,7 +9,7 @@ import MessagePill from 'components/MessagePill'; import MenuPageScriptItem from 'components/Root/components/MenuPageScriptItem'; import { AuditParametersType } from 'redux/entities/auditParameters/types'; import { ProjectType } from 'redux/entities/projects/types'; -import { UserState } from 'redux/user'; +import { useCurrentUser } from 'redux/user/selectors'; import { isUserAdminOfProject } from 'services/utils'; import { getSpacing } from 'stylesheet'; import { @@ -29,7 +29,6 @@ interface AuditParametersOption { } export interface OwnProps { - user: UserState; auditParametersId: string | null; currentPageId: string | null; project: ProjectType; @@ -43,7 +42,6 @@ export interface OwnProps { type Props = OwnProps; export const ProjectMenuContent: React.FunctionComponent = ({ - user, auditParametersId, currentPageId, project, @@ -53,6 +51,8 @@ export const ProjectMenuContent: React.FunctionComponent = ({ runningAudits, launchAudits, }) => { + const user = useCurrentUser(); + const [auditCanBeLaunched, setAuditCanBeLaunched] = React.useState(true); const auditParametersSelectOptions = auditParametersList.map(auditParameters => ({ diff --git a/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.wrap.tsx b/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.wrap.tsx index 4837c50be..9344a0014 100644 --- a/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.wrap.tsx +++ b/frontend/src/components/Root/components/Menu/components/ProjectMenuContent/ProjectMenuContent.wrap.tsx @@ -8,14 +8,12 @@ import { getCurrentScriptStepId, } from 'redux/parameters/selectors'; import { RootStateWithRouter } from 'redux/types'; -import { getUser } from 'redux/user/selectors'; import { Dispatch } from 'redux'; import { launchAuditAction } from 'redux/entities/audits'; import { ProjectMenuContent } from './ProjectMenuContent'; const mapStateToProps = (state: RootStateWithRouter) => ({ - user: getUser(state), auditParametersId: getCurrentAuditParametersId(state), currentPageId: getCurrentPageId(state), auditParametersList: getCurrentProjectAuditParameters(state), diff --git a/frontend/src/pages/Audits/Audits.tsx b/frontend/src/pages/Audits/Audits.tsx index 53e1d0a44..93ff2a3d0 100644 --- a/frontend/src/pages/Audits/Audits.tsx +++ b/frontend/src/pages/Audits/Audits.tsx @@ -4,7 +4,6 @@ import { ValueType } from 'react-select/lib/types'; import { AuditParametersType } from 'redux/entities/auditParameters/types'; import { PageType } from 'redux/entities/pages/types'; -import { ProjectType } from 'redux/entities/projects/types'; import { ScriptType } from 'redux/entities/scripts/types'; import Badge from 'components/Badge'; @@ -14,7 +13,7 @@ import Select from 'components/Select'; import dayjs from 'dayjs'; import { FormattedMessage, useIntl } from 'react-intl'; import { auditStatus, AuditStatusHistoryType } from 'redux/entities/auditStatusHistories/types'; -import { useFetchProjectIfUndefined } from 'redux/entities/projects/useFetchProjectIfUndefined'; +import { useProjectById } from 'redux/entities/projects/hooks'; import { routeDefinitions } from 'routes'; import { colorUsage, getSpacing } from 'stylesheet'; import AnalyticsBlock from './AnalyticsBlock'; @@ -41,14 +40,12 @@ export type OwnProps = {} & RouteComponentProps<{ }>; type Props = { - project?: ProjectType | null; page?: PageType | null; script?: ScriptType | null; currentAuditParameters?: AuditParametersType | null; scriptSteps: Record; sortedPageAuditResultsIds: string[] | null; sortedScriptAuditResultsIds: Record | null; - fetchProjectsRequest: (projectId: string) => void; pageAuditStatusHistory?: AuditStatusHistoryType | null; scriptAuditStatusHistory?: AuditStatusHistoryType | null; fetchAuditResultsRequest: ( @@ -66,10 +63,8 @@ type Props = { export const Audits: React.FunctionComponent = ({ currentAuditParameters, - fetchProjectsRequest, history, match, - project, page, script, scriptSteps, @@ -87,7 +82,7 @@ export const Audits: React.FunctionComponent = ({ const { projectId, pageOrScriptId, auditParametersId, scriptStepId } = match.params; - useFetchProjectIfUndefined(fetchProjectsRequest, projectId, project); + const project = useProjectById(projectId); React.useEffect( () => { diff --git a/frontend/src/pages/Audits/Audits.wrap.tsx b/frontend/src/pages/Audits/Audits.wrap.tsx index fc0e6b3e3..a201cacf9 100644 --- a/frontend/src/pages/Audits/Audits.wrap.tsx +++ b/frontend/src/pages/Audits/Audits.wrap.tsx @@ -9,8 +9,6 @@ import { } from 'redux/auditResults/selectors'; import { getAuditParameters } from 'redux/entities/auditParameters/selectors'; import { getPage, getPageLatestAuditStatusHistory } from 'redux/entities/pages/selectors'; -import { fetchProjectsRequest } from 'redux/entities/projects'; -import { getProject } from 'redux/entities/projects/selectors'; import { getScript, getScriptLatestAuditStatusHistory } from 'redux/entities/scripts/selectors'; import { setCurrentAuditParametersId, @@ -22,7 +20,6 @@ import { RootState } from 'redux/types'; import { Audits, OwnProps } from './Audits'; const mapStateToProps = (state: RootState, props: OwnProps) => ({ - project: getProject(state, props.match.params.projectId), page: getPage(state, props.match.params.pageOrScriptId), script: getScript(state, props.match.params.pageOrScriptId), currentAuditParameters: getAuditParameters(state, props.match.params.auditParametersId), @@ -59,8 +56,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch( fetchAuditResultsRequest({ auditParametersId, pageOrScriptId, type, fromDate, toDate }), ), - fetchProjectsRequest: (projectId: string) => - dispatch(fetchProjectsRequest({ currentProjectId: projectId })), setCurrentAuditParametersId: (auditParametersId: string | null | undefined) => dispatch(setCurrentAuditParametersId({ auditParametersId })), setCurrentPageId: (pageId: string | null | undefined) => dispatch(setCurrentPageId({ pageId })), diff --git a/frontend/src/pages/EnvironmentSettings/EnvironmentSettings.tsx b/frontend/src/pages/EnvironmentSettings/EnvironmentSettings.tsx index a6397623f..483b64c5e 100644 --- a/frontend/src/pages/EnvironmentSettings/EnvironmentSettings.tsx +++ b/frontend/src/pages/EnvironmentSettings/EnvironmentSettings.tsx @@ -5,9 +5,8 @@ import { FormattedMessage, useIntl } from 'react-intl'; import ReduxToastr, { toastr } from 'react-redux-toastr'; import 'react-redux-toastr/lib/css/react-redux-toastr.min.css'; import { RouteComponentProps } from 'react-router'; -import { ProjectToastrDisplayType, ProjectType } from 'redux/entities/projects/types'; -import { useFetchProjectIfUndefined } from 'redux/entities/projects/useFetchProjectIfUndefined'; -import { UserState } from 'redux/user'; +import { useProjectById, useToastr } from 'redux/entities/projects/hooks'; +import { useCurrentUser } from 'redux/user/selectors'; import { makeGetRequest } from 'services/networking/request'; import { isUserAdminOfProject } from 'services/utils'; import AuditParameterRow, { AddAuditParameterRow } from './Components/AuditParameterTable'; @@ -22,27 +21,14 @@ import { ProjectSettingsBlock, } from './EnvironmentSettings.style'; -export type OwnProps = {} & RouteComponentProps<{ +type Props = RouteComponentProps<{ projectId: string; }>; -type Props = { - currentUser: UserState; - fetchProjectsRequest: (projectId: string) => void; - project?: ProjectType | null; - toastrDisplay: ProjectToastrDisplayType; - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => void; -} & OwnProps; - -const EnvironmentSettings: React.FunctionComponent = ({ - fetchProjectsRequest, - match, - project, - currentUser, - toastrDisplay, - setProjectToastrDisplay, -}) => { +const EnvironmentSettings: React.FunctionComponent = ({ match }) => { const intl = useIntl(); + const currentUser = useCurrentUser(); + const { currentToastrDisplay, resetToastrDisplay } = useToastr(); interface UserOption { value: string; @@ -58,7 +44,7 @@ const EnvironmentSettings: React.FunctionComponent = ({ wpt_instance_url: string; } - useFetchProjectIfUndefined(fetchProjectsRequest, match.params.projectId, project); + const project = useProjectById(match.params.projectId); const [availableAuditParameters, setAvailableAuditParameters] = React.useState< Array<{ label: string; uuid: string; wptInstanceURL: string }> @@ -87,8 +73,8 @@ const EnvironmentSettings: React.FunctionComponent = ({ React.useEffect( () => { - if ('' !== toastrDisplay) { - switch (toastrDisplay) { + if ('' !== currentToastrDisplay) { + switch (currentToastrDisplay) { case 'addAuditParameterSuccess': toastr.success( intl.formatMessage({ id: 'Toastr.ProjectSettings.success_title' }), @@ -119,10 +105,10 @@ const EnvironmentSettings: React.FunctionComponent = ({ break; } - setProjectToastrDisplay(''); + resetToastrDisplay(); } }, - [toastrDisplay, setProjectToastrDisplay, intl], + [currentToastrDisplay, resetToastrDisplay, intl], ); if (project === undefined) { diff --git a/frontend/src/pages/EnvironmentSettings/EnvironmentSettings.wrap.tsx b/frontend/src/pages/EnvironmentSettings/EnvironmentSettings.wrap.tsx deleted file mode 100644 index 6d043205c..000000000 --- a/frontend/src/pages/EnvironmentSettings/EnvironmentSettings.wrap.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { RootState } from 'redux/types'; - -import { fetchProjectsRequest, setProjectToastrDisplay } from 'redux/entities/projects'; -import { getProject, getProjectToastrDisplay } from 'redux/entities/projects/selectors'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; -import { getUser } from 'redux/user/selectors'; -import EnvironmentSettings, { OwnProps } from './EnvironmentSettings'; - -const mapStateToProps = (state: RootState, props: OwnProps) => ({ - currentUser: getUser(state), - project: getProject(state, props.match.params.projectId), - toastrDisplay: getProjectToastrDisplay(state), -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - fetchProjectsRequest: (projectId: string) => - dispatch(fetchProjectsRequest({ currentProjectId: projectId })), - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => - dispatch(setProjectToastrDisplay({ toastrDisplay })), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(EnvironmentSettings); diff --git a/frontend/src/pages/EnvironmentSettings/index.tsx b/frontend/src/pages/EnvironmentSettings/index.tsx index 002bf511d..f20cbe019 100644 --- a/frontend/src/pages/EnvironmentSettings/index.tsx +++ b/frontend/src/pages/EnvironmentSettings/index.tsx @@ -1 +1 @@ -export { default } from './EnvironmentSettings.wrap'; +export { default } from './EnvironmentSettings'; diff --git a/frontend/src/pages/GeneralSettings/GeneralSettings.tsx b/frontend/src/pages/GeneralSettings/GeneralSettings.tsx index 092e98557..931b28529 100644 --- a/frontend/src/pages/GeneralSettings/GeneralSettings.tsx +++ b/frontend/src/pages/GeneralSettings/GeneralSettings.tsx @@ -5,9 +5,8 @@ import { FormattedMessage, useIntl } from 'react-intl'; import ReduxToastr, { toastr } from 'react-redux-toastr'; import 'react-redux-toastr/lib/css/react-redux-toastr.min.css'; import { RouteComponentProps } from 'react-router'; -import { ProjectToastrDisplayType, ProjectType } from 'redux/entities/projects/types'; -import { useFetchProjectIfUndefined } from 'redux/entities/projects/useFetchProjectIfUndefined'; -import { UserState } from 'redux/user'; +import { useProjectById, useToastr } from 'redux/entities/projects/hooks'; +import { useCurrentUser } from 'redux/user/selectors'; import ProjectDetailsInput from './Components/ProjectDetailsInput'; import { Container, @@ -17,32 +16,19 @@ import { Title, } from './GeneralSettings.style'; -export type OwnProps = {} & RouteComponentProps<{ - projectId: string; -}>; - type Props = { - currentUser: UserState; - fetchProjectsRequest: (projectId: string) => void; - project?: ProjectType | null; - toastrDisplay: ProjectToastrDisplayType; - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => void; editProjectDetailsRequest: ( projectId: string, payload: { name: string; wpt_api_key: string; wpt_instance_url: string }, ) => void; -} & OwnProps; - -const GeneralSettings: React.FunctionComponent = ({ - fetchProjectsRequest, - match, - project, - currentUser, - toastrDisplay, - setProjectToastrDisplay, - editProjectDetailsRequest, -}) => { +} & RouteComponentProps<{ + projectId: string; +}>; + +const GeneralSettings: React.FunctionComponent = ({ match, editProjectDetailsRequest }) => { const intl = useIntl(); + const currentUser = useCurrentUser(); + const { currentToastrDisplay, resetToastrDisplay } = useToastr(); interface UserOption { value: string; @@ -57,7 +43,7 @@ const GeneralSettings: React.FunctionComponent = ({ location_group: string; } - useFetchProjectIfUndefined(fetchProjectsRequest, match.params.projectId, project); + const project = useProjectById(match.params.projectId); const [projectName, setProjectName] = React.useState(''); const [projectApiKey, setProjectApiKey] = React.useState(''); @@ -74,8 +60,8 @@ const GeneralSettings: React.FunctionComponent = ({ React.useEffect( () => { - if ('' !== toastrDisplay) { - switch (toastrDisplay) { + if ('' !== currentToastrDisplay) { + switch (currentToastrDisplay) { case 'editProjectDetailsSuccess': toastr.success( intl.formatMessage({ id: 'Toastr.ProjectSettings.success_title' }), @@ -90,10 +76,10 @@ const GeneralSettings: React.FunctionComponent = ({ break; } - setProjectToastrDisplay(''); + resetToastrDisplay(); } }, - [toastrDisplay, setProjectToastrDisplay, intl], + [currentToastrDisplay, resetToastrDisplay, intl], ); if (project === undefined) { diff --git a/frontend/src/pages/GeneralSettings/GeneralSettings.wrap.tsx b/frontend/src/pages/GeneralSettings/GeneralSettings.wrap.tsx index 53495a880..4e76ce4fb 100644 --- a/frontend/src/pages/GeneralSettings/GeneralSettings.wrap.tsx +++ b/frontend/src/pages/GeneralSettings/GeneralSettings.wrap.tsx @@ -1,28 +1,10 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { RootState } from 'redux/types'; -import { - editProjectDetailsRequest, - fetchProjectsRequest, - setProjectToastrDisplay, -} from 'redux/entities/projects'; -import { getProject, getProjectToastrDisplay } from 'redux/entities/projects/selectors'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; -import { getUser } from 'redux/user/selectors'; -import GeneralSettings, { OwnProps } from './GeneralSettings'; - -const mapStateToProps = (state: RootState, props: OwnProps) => ({ - currentUser: getUser(state), - project: getProject(state, props.match.params.projectId), - toastrDisplay: getProjectToastrDisplay(state), -}); +import { editProjectDetailsRequest } from 'redux/entities/projects'; +import GeneralSettings from './GeneralSettings'; const mapDispatchToProps = (dispatch: Dispatch) => ({ - fetchProjectsRequest: (projectId: string) => - dispatch(fetchProjectsRequest({ currentProjectId: projectId })), - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => - dispatch(setProjectToastrDisplay({ toastrDisplay })), editProjectDetailsRequest: ( projectId: string, payload: { name: string; wpt_api_key: string; wpt_instance_url: string }, @@ -30,6 +12,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ }); export default connect( - mapStateToProps, + null, mapDispatchToProps, )(GeneralSettings); diff --git a/frontend/src/pages/MembersSettings/MembersSettings.tsx b/frontend/src/pages/MembersSettings/MembersSettings.tsx index 0f5cb671f..8ceaa3f58 100644 --- a/frontend/src/pages/MembersSettings/MembersSettings.tsx +++ b/frontend/src/pages/MembersSettings/MembersSettings.tsx @@ -9,14 +9,10 @@ import ReduxToastr, { toastr } from 'react-redux-toastr'; import 'react-redux-toastr/lib/css/react-redux-toastr.min.css'; import { RouteComponentProps } from 'react-router'; import { ValueType } from 'react-select/lib/types'; -import { - ProjectMember, - ProjectToastrDisplayType, - ProjectType, -} from 'redux/entities/projects/types'; -import { useFetchProjectIfUndefined } from 'redux/entities/projects/useFetchProjectIfUndefined'; -import { UserState } from 'redux/user'; +import { useProjectById, useToastr } from 'redux/entities/projects/hooks'; +import { ProjectMember } from 'redux/entities/projects/types'; import { modelizeUser } from 'redux/user/modelizer'; +import { useCurrentUser } from 'redux/user/selectors'; import { ApiUser, User } from 'redux/user/types'; import { makeGetRequest } from 'services/networking/request'; import { isUserAdminOfProject } from 'services/utils'; @@ -36,31 +32,19 @@ import { SelectUser, } from './MembersSettings.style'; -export type OwnProps = {} & RouteComponentProps<{ - projectId: string; -}>; - type Props = { - currentUser: UserState; addMemberToProject: (projectId: string, userId: string) => void; removeMemberOfProjectRequest: (projectId: string, userId: string) => void; editMemberOfProjectRequest: (projectId: string, userId: string, isAdmin: boolean) => void; - fetchProjectsRequest: (projectId: string) => void; - project?: ProjectType | null; - toastrDisplay: ProjectToastrDisplayType; - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => void; -} & OwnProps; +} & RouteComponentProps<{ + projectId: string; +}>; const MembersSettings: React.FunctionComponent = ({ addMemberToProject, removeMemberOfProjectRequest, editMemberOfProjectRequest, - fetchProjectsRequest, match, - project, - currentUser, - toastrDisplay, - setProjectToastrDisplay, }) => { const intl = useIntl(); @@ -70,9 +54,15 @@ const MembersSettings: React.FunctionComponent = ({ disabled: boolean; } + const currentUser = useCurrentUser(); + + const { currentToastrDisplay, resetToastrDisplay } = useToastr(); + const [selectOption, setSelectOption]: [ValueType, any] = React.useState(null); const [allUsers, setAllUsers] = React.useState([]); + const project = useProjectById(match.params.projectId); + const fetchAllUsers = () => { const request = makeGetRequest('/api/core/users', true); request.then(response => { @@ -93,12 +83,10 @@ const MembersSettings: React.FunctionComponent = ({ [project], ); - useFetchProjectIfUndefined(fetchProjectsRequest, match.params.projectId, project); - React.useEffect( () => { - if ('' !== toastrDisplay) { - switch (toastrDisplay) { + if ('' !== currentToastrDisplay) { + switch (currentToastrDisplay) { case 'addMemberSuccess': toastr.success( intl.formatMessage({ id: 'Toastr.ProjectSettings.success_title' }), @@ -113,10 +101,10 @@ const MembersSettings: React.FunctionComponent = ({ break; } - setProjectToastrDisplay(''); + resetToastrDisplay(); } }, - [toastrDisplay, setProjectToastrDisplay, intl], + [currentToastrDisplay, resetToastrDisplay, intl], ); const onChange = (selectedOption: ValueType) => { diff --git a/frontend/src/pages/MembersSettings/MembersSettings.wrap.tsx b/frontend/src/pages/MembersSettings/MembersSettings.wrap.tsx index 4e74c062e..9a7df5050 100644 --- a/frontend/src/pages/MembersSettings/MembersSettings.wrap.tsx +++ b/frontend/src/pages/MembersSettings/MembersSettings.wrap.tsx @@ -1,24 +1,12 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { RootState } from 'redux/types'; import { addMemberToProjectRequest, deleteMemberOfProjectRequest, editMemberOfProjectRequest, - fetchProjectsRequest, - setProjectToastrDisplay, } from 'redux/entities/projects'; -import { getProject, getProjectToastrDisplay } from 'redux/entities/projects/selectors'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; -import { getUser } from 'redux/user/selectors'; -import MembersSettings, { OwnProps } from './MembersSettings'; - -const mapStateToProps = (state: RootState, props: OwnProps) => ({ - currentUser: getUser(state), - project: getProject(state, props.match.params.projectId), - toastrDisplay: getProjectToastrDisplay(state), -}); +import MembersSettings from './MembersSettings'; const mapDispatchToProps = (dispatch: Dispatch) => ({ addMemberToProject: (projectId: string, userId: string) => @@ -27,13 +15,9 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch(deleteMemberOfProjectRequest({ projectId, userId })), editMemberOfProjectRequest: (projectId: string, userId: string, isAdmin: boolean) => dispatch(editMemberOfProjectRequest({ projectId, userId, isAdmin })), - fetchProjectsRequest: (projectId: string) => - dispatch(fetchProjectsRequest({ currentProjectId: projectId })), - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => - dispatch(setProjectToastrDisplay({ toastrDisplay })), }); export default connect( - mapStateToProps, + null, mapDispatchToProps, )(MembersSettings); diff --git a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.tsx b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.tsx index 60fdea290..452f758c3 100644 --- a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.tsx +++ b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.tsx @@ -4,7 +4,7 @@ import Modal from 'react-modal'; import { useAsyncFn } from 'react-use'; import Close from 'icons/Close'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; +import { useToastr } from 'redux/entities/projects/hooks'; import { modelizeScript } from 'redux/entities/scripts/modelizer'; import { ScriptType } from 'redux/entities/scripts/types'; import { makePostRequest, makePutRequest } from 'services/networking/request'; @@ -30,7 +30,6 @@ type Props = { addScriptToProjectSuccess: (projectId: string, scriptId: string) => void; addScript: (byId: Record) => void; editScriptSuccess: (byId: Record) => void; - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => void; } & OwnProps; export const ScriptModal: React.FunctionComponent = ({ @@ -42,13 +41,14 @@ export const ScriptModal: React.FunctionComponent = ({ addScriptToProjectSuccess, addScript, editScriptSuccess, - setProjectToastrDisplay, }) => { const intl = useIntl(); const [scriptContent, setScriptContent] = React.useState(script ? script.script : ''); const [scriptName, setScriptName] = React.useState(script ? script.name : ''); + const { setToastrDisplay } = useToastr(); + const [state, createScript] = useAsyncFn( async () => { try { @@ -62,10 +62,10 @@ export const ScriptModal: React.FunctionComponent = ({ const modelizedScript = modelizeScript(response.body); addScript({ [modelizedScript.uuid]: modelizedScript }); addScriptToProjectSuccess(projectId, modelizedScript.uuid); - setProjectToastrDisplay('addScriptToProjectSuccess'); + setToastrDisplay('addScriptToProjectSuccess'); close(); } catch (e) { - setProjectToastrDisplay('addScriptToProjectError'); + setToastrDisplay('addScriptToProjectError'); } }, [projectId, scriptContent, scriptName], @@ -86,11 +86,11 @@ export const ScriptModal: React.FunctionComponent = ({ throw new Error('No response'); } const modelizedScript = modelizeScript(response.body); - setProjectToastrDisplay('editScriptSuccess'); + setToastrDisplay('editScriptSuccess'); editScriptSuccess({ [modelizedScript.uuid]: modelizedScript }); close(); } catch (e) { - setProjectToastrDisplay('editScriptError'); + setToastrDisplay('editScriptError'); } }, [projectId, scriptContent, scriptName], diff --git a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.wrap.tsx b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.wrap.tsx index 1f6d31373..ce5b3f342 100644 --- a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.wrap.tsx +++ b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptModal/ScriptModal.wrap.tsx @@ -1,7 +1,6 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { addScriptToProjectSuccess, setProjectToastrDisplay } from 'redux/entities/projects'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; +import { addScriptToProjectSuccess } from 'redux/entities/projects'; import { addScript, editScriptSuccess } from 'redux/entities/scripts'; import { getScript } from 'redux/entities/scripts/selectors'; import { ScriptType } from 'redux/entities/scripts/types'; @@ -17,8 +16,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch(addScriptToProjectSuccess({ projectId, scriptId })), addScript: (byId: Record) => dispatch(addScript({ byId })), editScriptSuccess: (byId: Record) => dispatch(editScriptSuccess({ byId })), - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => - dispatch(setProjectToastrDisplay({ toastrDisplay })), }); export default connect( diff --git a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.tsx b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.tsx index fcc2d5c4d..9eed5312a 100644 --- a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.tsx +++ b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { useIntl } from 'react-intl'; import { toastr } from 'react-redux-toastr'; import { useAsyncFn } from 'react-use'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; +import { useToastr } from 'redux/entities/projects/hooks'; import { ScriptType } from 'redux/entities/scripts/types'; import { makeDeleteRequest } from 'services/networking/request'; import { colorUsage } from 'stylesheet'; @@ -24,24 +24,21 @@ export interface OwnProps { type Props = { script: ScriptType | null | undefined; - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => void; deleteScriptFromProjectSuccess: (payload: { projectId: string; scriptId: string }) => void; } & OwnProps; export const ScriptRow: React.FunctionComponent = ({ scriptId, projectId, - setProjectToastrDisplay, deleteScriptFromProjectSuccess, script, }) => { const intl = useIntl(); + const { setToastrDisplay } = useToastr(); const [displayScriptModal, setDisplayScriptModal] = React.useState(false); - // could’t find a way to not declare `state` using `useAsyncFn` - // eslint-disable-next-line - const [state, deleteScript] = useAsyncFn( + const [, deleteScript] = useAsyncFn( async () => { try { const response = await makeDeleteRequest( @@ -52,12 +49,12 @@ export const ScriptRow: React.FunctionComponent = ({ throw new Error('No response'); } deleteScriptFromProjectSuccess({ projectId, scriptId }); - setProjectToastrDisplay('deleteScriptSuccess'); + setToastrDisplay('deleteScriptSuccess'); } catch (e) { - setProjectToastrDisplay('deleteScriptError'); + setToastrDisplay('deleteScriptError'); } }, - [projectId, scriptId], + [projectId, scriptId, setToastrDisplay], ); const openScriptModal = () => { diff --git a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.wrap.tsx b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.wrap.tsx index 90e71991b..eb4786ea7 100644 --- a/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.wrap.tsx +++ b/frontend/src/pages/PagesAndScriptsSettings/Components/ScriptTable/ScriptRow/ScriptRow.wrap.tsx @@ -1,8 +1,6 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { setProjectToastrDisplay } from 'redux/entities/projects'; import { deleteScriptFromProjectSuccess } from 'redux/entities/projects/actions'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; import { getScript } from 'redux/entities/scripts/selectors'; import { RootState } from 'redux/types'; import { OwnProps, ScriptRow } from './ScriptRow'; @@ -14,8 +12,6 @@ const mapStateToProps = (state: RootState, props: OwnProps) => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({ deleteScriptFromProjectSuccess: (payload: { projectId: string; scriptId: string }) => dispatch(deleteScriptFromProjectSuccess(payload)), - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => - dispatch(setProjectToastrDisplay({ toastrDisplay })), }); export default connect( diff --git a/frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.tsx b/frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.tsx index d0419be7a..e6de6042d 100644 --- a/frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.tsx +++ b/frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.tsx @@ -5,9 +5,8 @@ import { FormattedMessage, useIntl } from 'react-intl'; import ReduxToastr, { toastr } from 'react-redux-toastr'; import 'react-redux-toastr/lib/css/react-redux-toastr.min.css'; import { RouteComponentProps } from 'react-router'; -import { ProjectToastrDisplayType, ProjectType } from 'redux/entities/projects/types'; -import { useFetchProjectIfUndefined } from 'redux/entities/projects/useFetchProjectIfUndefined'; -import { UserState } from 'redux/user'; +import { useProjectById, useToastr } from 'redux/entities/projects/hooks'; +import { useCurrentUser } from 'redux/user/selectors'; import { isUserAdminOfProject } from 'services/utils'; import { AddPageRow } from './Components/PageTable'; import PageRow, { PageRowHeader } from './Components/PageTable'; @@ -20,27 +19,14 @@ import { ProjectSettingsBlock, } from './PagesAndScriptsSettings.style'; -export type OwnProps = {} & RouteComponentProps<{ +type Props = RouteComponentProps<{ projectId: string; }>; -type Props = { - currentUser: UserState; - fetchProjectsRequest: (projectId: string) => void; - project?: ProjectType | null; - toastrDisplay: ProjectToastrDisplayType; - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => void; -} & OwnProps; - -const PagesAndScriptsSettings: React.FunctionComponent = ({ - fetchProjectsRequest, - match, - project, - currentUser, - toastrDisplay, - setProjectToastrDisplay, -}) => { +const PagesAndScriptsSettings: React.FunctionComponent = ({ match }) => { const intl = useIntl(); + const currentUser = useCurrentUser(); + const { currentToastrDisplay, resetToastrDisplay } = useToastr(); interface UserOption { value: string; @@ -55,12 +41,12 @@ const PagesAndScriptsSettings: React.FunctionComponent = ({ location_group: string; } - useFetchProjectIfUndefined(fetchProjectsRequest, match.params.projectId, project); + const project = useProjectById(match.params.projectId); React.useEffect( () => { - if ('' !== toastrDisplay) { - switch (toastrDisplay) { + if ('' !== currentToastrDisplay) { + switch (currentToastrDisplay) { case 'addPageSuccess': toastr.success( intl.formatMessage({ id: 'Toastr.ProjectSettings.success_title' }), @@ -110,10 +96,10 @@ const PagesAndScriptsSettings: React.FunctionComponent = ({ break; } - setProjectToastrDisplay(''); + resetToastrDisplay(); } }, - [toastrDisplay, setProjectToastrDisplay, intl], + [currentToastrDisplay, resetToastrDisplay, intl], ); if (project === undefined) { diff --git a/frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.wrap.tsx b/frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.wrap.tsx deleted file mode 100644 index 0f0092312..000000000 --- a/frontend/src/pages/PagesAndScriptsSettings/PagesAndScriptsSettings.wrap.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { RootState } from 'redux/types'; - -import { fetchProjectsRequest, setProjectToastrDisplay } from 'redux/entities/projects'; -import { getProject, getProjectToastrDisplay } from 'redux/entities/projects/selectors'; -import { ProjectToastrDisplayType } from 'redux/entities/projects/types'; -import { getUser } from 'redux/user/selectors'; -import PagesAndScriptsSettings, { OwnProps } from './PagesAndScriptsSettings'; - -const mapStateToProps = (state: RootState, props: OwnProps) => ({ - currentUser: getUser(state), - project: getProject(state, props.match.params.projectId), - toastrDisplay: getProjectToastrDisplay(state), -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - fetchProjectsRequest: (projectId: string) => - dispatch(fetchProjectsRequest({ currentProjectId: projectId })), - setProjectToastrDisplay: (toastrDisplay: ProjectToastrDisplayType) => - dispatch(setProjectToastrDisplay({ toastrDisplay })), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(PagesAndScriptsSettings); diff --git a/frontend/src/pages/PagesAndScriptsSettings/index.tsx b/frontend/src/pages/PagesAndScriptsSettings/index.tsx index fb6d79ec2..3dbc4b63d 100644 --- a/frontend/src/pages/PagesAndScriptsSettings/index.tsx +++ b/frontend/src/pages/PagesAndScriptsSettings/index.tsx @@ -1 +1 @@ -export { default } from './PagesAndScriptsSettings.wrap'; +export { default } from './PagesAndScriptsSettings'; diff --git a/frontend/src/pages/Project/Project.tsx b/frontend/src/pages/Project/Project.tsx index 1c132fb16..b539b8813 100644 --- a/frontend/src/pages/Project/Project.tsx +++ b/frontend/src/pages/Project/Project.tsx @@ -1,37 +1,30 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { Redirect, RouteComponentProps } from 'react-router'; -import { ProjectType } from 'redux/entities/projects/types'; import { routeDefinitions } from 'routes'; import Loader from 'components/Loader'; import MessagePill from 'components/MessagePill'; -import { useFetchProjectIfUndefined } from 'redux/entities/projects/useFetchProjectIfUndefined'; +import { useProjectById } from 'redux/entities/projects/hooks'; import { Container } from './Project.style'; -export type OwnProps = {} & RouteComponentProps<{ - projectId: string; -}>; - type Props = { - fetchProjectsRequest: (projectId: string) => void; setCurrentAuditParametersId: (auditParametersId: string | null | undefined) => void; setCurrentPageId: (pageId: string | null | undefined) => void; setCurrentScriptId: (scriptId: string | null | undefined) => void; setCurrentScriptStepId: (scriptStepId: string | null | undefined) => void; - project?: ProjectType | null; -} & OwnProps; +} & RouteComponentProps<{ + projectId: string; +}>; const Project: React.FunctionComponent = ({ - fetchProjectsRequest, - project, match, setCurrentAuditParametersId, setCurrentPageId, setCurrentScriptId, setCurrentScriptStepId, }) => { - useFetchProjectIfUndefined(fetchProjectsRequest, match.params.projectId, project); + const project = useProjectById(match.params.projectId); React.useEffect( () => { diff --git a/frontend/src/pages/Project/Project.wrap.tsx b/frontend/src/pages/Project/Project.wrap.tsx index e22e1d152..7b11be0bd 100644 --- a/frontend/src/pages/Project/Project.wrap.tsx +++ b/frontend/src/pages/Project/Project.wrap.tsx @@ -7,19 +7,9 @@ import { setCurrentScriptStepId, } from 'redux/parameters'; -import { fetchProjectsRequest } from 'redux/entities/projects'; -import { RootState } from 'redux/types'; - -import { getProject } from 'redux/entities/projects/selectors'; -import Project, { OwnProps } from './Project'; - -const mapStateToProps = (state: RootState, props: OwnProps) => ({ - project: getProject(state, props.match.params.projectId), -}); +import Project from './Project'; const mapDispatchToProps = (dispatch: Dispatch) => ({ - fetchProjectsRequest: (projectId: string) => - dispatch(fetchProjectsRequest({ currentProjectId: projectId })), setCurrentAuditParametersId: (auditParametersId: string | null | undefined) => dispatch(setCurrentAuditParametersId({ auditParametersId })), setCurrentPageId: (pageId: string | null | undefined) => dispatch(setCurrentPageId({ pageId })), @@ -30,6 +20,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ }); export default connect( - mapStateToProps, + null, mapDispatchToProps, )(Project); diff --git a/frontend/src/pages/Projects/Projects.tsx b/frontend/src/pages/Projects/Projects.tsx index b23fcbab3..5c4b9aa40 100644 --- a/frontend/src/pages/Projects/Projects.tsx +++ b/frontend/src/pages/Projects/Projects.tsx @@ -1,20 +1,14 @@ import * as React from 'react'; import { Redirect } from 'react-router'; -import { ProjectType } from 'redux/entities/projects/types'; import { routeDefinitions } from 'routes'; import Loader from 'components/Loader'; import Welcome from 'components/Welcome'; -import { useFetchProjectIfUndefined } from 'redux/entities/projects/useFetchProjectIfUndefined'; +import { useAllProjects } from 'redux/entities/projects/hooks'; import { Container } from './Projects.style'; -type Props = { - fetchProjectsRequest: () => void; - projects: Array | null; -}; - -const Projects: React.FunctionComponent = ({ fetchProjectsRequest, projects }) => { - useFetchProjectIfUndefined(fetchProjectsRequest, '', undefined); +const Projects: React.FunctionComponent = () => { + const projects = useAllProjects(); if (null === projects) { return ( diff --git a/frontend/src/pages/Projects/Projects.wrap.tsx b/frontend/src/pages/Projects/Projects.wrap.tsx deleted file mode 100644 index 6749af9b5..000000000 --- a/frontend/src/pages/Projects/Projects.wrap.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { fetchProjectsRequest } from 'redux/entities/projects'; -import { getAllProjects } from 'redux/entities/projects/selectors'; -import { RootState } from 'redux/types'; -import Projects from './Projects'; - -const mapStateToProps = (state: RootState) => ({ - projects: getAllProjects(state), -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - fetchProjectsRequest: () => dispatch(fetchProjectsRequest({})), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Projects); diff --git a/frontend/src/pages/Projects/index.tsx b/frontend/src/pages/Projects/index.tsx index db931645f..3b68dc8cf 100644 --- a/frontend/src/pages/Projects/index.tsx +++ b/frontend/src/pages/Projects/index.tsx @@ -1 +1 @@ -export { default } from './Projects.wrap'; +export { default } from './Projects'; diff --git a/frontend/src/redux/entities/projects/hooks.ts b/frontend/src/redux/entities/projects/hooks.ts new file mode 100644 index 000000000..71dc3a5e8 --- /dev/null +++ b/frontend/src/redux/entities/projects/hooks.ts @@ -0,0 +1,60 @@ +import { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { RootState } from 'redux/types'; + +import { fetchProjectsRequest, setProjectToastrDisplay } from './actions'; +import { getAllProjects, getProject, getProjectToastrDisplay } from './selectors'; +import { ProjectToastrDisplayType } from './types'; + +export const useAllProjects = () => { + const projects = useSelector(getAllProjects); + + const dispatch = useDispatch(); + useEffect( + () => { + if (!projects) { + dispatch(fetchProjectsRequest({})); + } + }, + [dispatch, projects], + ); + + return projects; +}; + +export const useProjectById = (projectId: string) => { + const project = useSelector((state: RootState) => getProject(state, projectId)); + + const dispatch = useDispatch(); + useEffect( + () => { + if (project === undefined) { + dispatch(fetchProjectsRequest({ currentProjectId: projectId })); + } + }, + [dispatch, project, projectId], + ); + + return project; +}; + +export const useToastr = () => { + const currentToastrDisplay = useSelector(getProjectToastrDisplay); + + const dispatch = useDispatch(); + const setToastrDisplay = useCallback( + (display: ProjectToastrDisplayType) => { + dispatch(setProjectToastrDisplay({ toastrDisplay: display })); + }, + [dispatch], + ); + const resetToastrDisplay = useCallback( + () => { + dispatch(setProjectToastrDisplay({ toastrDisplay: '' })); + }, + [dispatch], + ); + + return { currentToastrDisplay, setToastrDisplay, resetToastrDisplay }; +}; diff --git a/frontend/src/redux/entities/projects/useFetchProjectIfUndefined.ts b/frontend/src/redux/entities/projects/useFetchProjectIfUndefined.ts deleted file mode 100644 index ef757ee35..000000000 --- a/frontend/src/redux/entities/projects/useFetchProjectIfUndefined.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useEffect } from "react"; -import { ProjectType } from "./types"; - -export const useFetchProjectIfUndefined = ( - fetchProjectsRequest: (projectId: string) => void, - projectId: string, - project?: ProjectType | null, -) => { - useEffect(() => { - if (project === undefined) { - fetchProjectsRequest(projectId); - } - }, [fetchProjectsRequest, project, projectId]); -}; diff --git a/frontend/src/redux/user/selectors.ts b/frontend/src/redux/user/selectors.ts index a898f050a..608d5df98 100644 --- a/frontend/src/redux/user/selectors.ts +++ b/frontend/src/redux/user/selectors.ts @@ -1,3 +1,6 @@ +import { useSelector } from 'react-redux'; import { RootState } from 'redux/types'; -export const getUser = (store: RootState) => store.user; +const getUser = (store: RootState) => store.user; + +export const useCurrentUser = () => useSelector(getUser); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index be2ecf5d0..97dc6856c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1023,6 +1023,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.5.5": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2": version "7.7.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.7.tgz#194769ca8d6d7790ec23605af9ee3e42a0aa79cf" @@ -1658,7 +1665,7 @@ version "4.7.2" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220" -"@types/hoist-non-react-statics@^3.3.1": +"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -1770,11 +1777,14 @@ "@types/react" "*" redux "^3.6.0 || ^4.0.0" -"@types/react-redux@^6.0.12": - version "6.0.14" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-6.0.14.tgz#8ffeb52c4f4d578bf74418d9aff5af7289924e4f" +"@types/react-redux@^7.0.0": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.7.tgz#12a0c529aba660696947384a059c5c6e08185c7a" + integrity sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg== dependencies: + "@types/hoist-non-react-statics" "^3.3.0" "@types/react" "*" + hoist-non-react-statics "^3.3.0" redux "^4.0.0" "@types/react-router-dom@^4.3.1": @@ -3518,12 +3528,12 @@ connect-history-api-fallback@^1.6.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== -connected-react-router@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-5.0.1.tgz#8379854fad7e027b1e27652c00ad534f8ad244b3" +connected-react-router@^6.0.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.8.0.tgz#ddc687b31d498322445d235d660798489fa56cae" + integrity sha512-E64/6krdJM3Ag3MMmh2nKPtMbH15s3JQDuaYJvOVXzu6MbHbDyIvuwLOyhQIuP4Om9zqEfZYiVyflROibSsONg== dependencies: - immutable "^3.8.1" - seamless-immutable "^7.1.3" + prop-types "^15.7.2" console-browserify@^1.1.0: version "1.1.0" @@ -5946,12 +5956,6 @@ hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" -hoist-non-react-statics@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" - dependencies: - react-is "^16.7.0" - hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -6164,10 +6168,6 @@ immer@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" -immutable@^3.8.1: - version "3.8.2" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" - import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -10244,6 +10244,11 @@ react-is@^16.6.0, react-is@^16.6.1, react-is@^16.6.3, react-is@^16.7.0, react-is version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" +react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -10264,17 +10269,16 @@ react-redux-toastr@^7.5.1: classnames "^2.2.3" eventemitter3 "^3.1.0" -react-redux@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.0.tgz#948b1e2686473d1999092bcfb32d0dc43d33f667" +react-redux@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== dependencies: - "@babel/runtime" "^7.1.2" - hoist-non-react-statics "^3.0.0" - invariant "^2.2.4" - loose-envify "^1.1.0" - prop-types "^15.6.1" - react-is "^16.6.0" - react-lifecycles-compat "^3.0.0" + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" react-resize-detector@^2.3.0: version "2.3.0" @@ -10657,6 +10661,11 @@ regenerator-runtime@^0.13.3: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + regenerator-transform@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.0.tgz#2ca9aaf7a2c239dd32e4761218425b8c7a86ecaf" @@ -11136,10 +11145,6 @@ screenfull@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.0.tgz#5c2010c0e84fd4157bf852877698f90b8cbe96f6" -seamless-immutable@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/seamless-immutable/-/seamless-immutable-7.1.4.tgz#6e9536def083ddc4dea0207d722e0e80d0f372f8" - select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"