diff --git a/.eslintrc.js b/.eslintrc.js index 88b5bcd2da30e71fc6f341274fcf999c65197e13..7026b07766398eb13a968ec606bdf0d1dc1b8918 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,6 +50,18 @@ module.exports = { 'no-param-reassign': 'warn', 'spaced-comment': ['error', 'always', { block: { exceptions: ['*'] } }], 'react/self-closing-comp': 'warn', + + // Rule to suggest using useAppDispatch instead of regular useDispatch + 'no-restricted-imports': 'off', + '@typescript-eslint/no-restricted-imports': [ + 'warn', + { + name: 'react-redux', + importNames: ['useSelector', 'useDispatch'], + message: + 'Use typed hooks `useAppDispatch` and `useAppSelector` instead.', + }, + ], }, root: true, settings: { diff --git a/package.json b/package.json index 4bdb4bd440f8c8746b6fed375ef27b89db2a31fa..53851cd0b17154b087b3a581c7eceaa38baf8c01 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@cozy/minilog": "^1.0.0", "@material-ui/core": "~4.12.0", "@material-ui/styles": "^4.11.3", - "@reduxjs/toolkit": "^1.9.3", + "@reduxjs/toolkit": "^1.9.5", "@sentry/react": "^7.21.1", "@sentry/tracing": "^7.21.1", "@simbathesailor/use-what-changed": "^2.0.0", @@ -78,7 +78,7 @@ "object-hash": "^3.0.0", "react": "16.14.0", "react-dom": "16.14.0", - "react-redux": "^7.2.2", + "react-redux": "^8.1.2", "react-router-dom": "^6.6.1", "react-swipeable-views": "0.14.0", "redux-devtools-extension": "^2.13.8", @@ -102,7 +102,6 @@ "@types/react": "^16.9.15", "@types/react-dom": "^16.9.8", "@types/react-lottie": "^1.2.3", - "@types/react-redux": "^7.1.11", "@types/react-router-dom": "^5.3.3", "@types/redux-mock-store": "^1.0.2", "@typescript-eslint/eslint-plugin": "^5.56.0", diff --git a/src/components/Konnector/KonnectorViewerCard.tsx b/src/components/Konnector/KonnectorViewerCard.tsx index a6fcdf8110da707b983bdf6fe424ecade380fcaf..d7498a3c102b42be33409527002fcd3d7e5ba0e8 100644 --- a/src/components/Konnector/KonnectorViewerCard.tsx +++ b/src/components/Konnector/KonnectorViewerCard.tsx @@ -221,11 +221,7 @@ const KonnectorViewerCard = ({ // RESTORE LAST KNOWN CREDENTIALS if (lastEpglLogin) { - dispatch( - setLastEpglLogin({ - lastEpglLogin: lastEpglLogin, - }) - ) + dispatch(setLastEpglLogin(lastEpglLogin)) } } else { if (isSuccess && currentFluidStatus.lastDataDate === null) { diff --git a/src/store/analysis/analysis.slice.ts b/src/store/analysis/analysis.slice.ts index b3fdf4b33b5fd55d09d912a8ebe22b3fcd40d3c4..11eabac17c89f295762c5bf02b5ecdc10c45a866 100644 --- a/src/store/analysis/analysis.slice.ts +++ b/src/store/analysis/analysis.slice.ts @@ -7,19 +7,14 @@ const initialState: AnalysisState = { analysisMonth: DateTime.local().minus({ months: 1 }).startOf('day'), } -type SetPeriodAction = PayloadAction<'month' | 'year'> -type SetSelectedMonthAction = PayloadAction<DateTime> - -export type AnalysisActionTypes = SetPeriodAction | SetSelectedMonthAction - export const analysisSlice = createSlice({ name: 'analysis', initialState, reducers: { - setPeriod: (state, action: SetPeriodAction) => { + setPeriod: (state, action: PayloadAction<'month' | 'year'>) => { state.period = action.payload }, - setAnalysisMonth: (state, action: SetSelectedMonthAction) => { + setAnalysisMonth: (state, action: PayloadAction<DateTime>) => { state.analysisMonth = action.payload }, }, diff --git a/src/store/challenge/challenge.slice.ts b/src/store/challenge/challenge.slice.ts index 9f37d4f90f5b29bcc64635c1558ce4ffc661df81..8b5d33573f11aaeff89859ca72bba5552b9ed2f2 100644 --- a/src/store/challenge/challenge.slice.ts +++ b/src/store/challenge/challenge.slice.ts @@ -8,25 +8,16 @@ const initialState: ChallengeState = { currentDataload: [], } -type SetUserChallengeList = PayloadAction<UserChallenge[]> -type UpdateUserChallengeList = PayloadAction<UserChallenge> -type UnlockNextUserChallenge = PayloadAction<UserChallenge> type SetChallengeConsumption = PayloadAction<{ userChallenge: UserChallenge currentDataload: Dataload[] }> -export type ChallengeActionTypes = - | SetUserChallengeList - | UpdateUserChallengeList - | UnlockNextUserChallenge - | SetChallengeConsumption - export const challengeSlice = createSlice({ name: 'challenge', initialState, reducers: { - setUserChallengeList: (state, action: SetUserChallengeList) => { + setUserChallengeList: (state, action: PayloadAction<UserChallenge[]>) => { const filteredCurrentChallenge = action.payload.filter( challenge => challenge.state === UserChallengeState.ONGOING || @@ -37,7 +28,7 @@ export const challengeSlice = createSlice({ state.userChallengeList = action.payload state.currentChallenge = currentChallenge }, - updateUserChallengeList: (state, action: UpdateUserChallengeList) => { + updateUserChallengeList: (state, action: PayloadAction<UserChallenge>) => { const id = action.payload.id const currentChallenge = action.payload.state === UserChallengeState.ONGOING || @@ -51,7 +42,7 @@ export const challengeSlice = createSlice({ state.userChallengeList = updatedList state.currentChallenge = currentChallenge || state.currentChallenge }, - unlockNextUserChallenge: (state, action: UnlockNextUserChallenge) => { + unlockNextUserChallenge: (state, action: PayloadAction<UserChallenge>) => { const id = action.payload.id const updatedList = [...state.userChallengeList] const findIndex = updatedList.findIndex(challenge => challenge.id === id) diff --git a/src/store/chart/chart.slice.ts b/src/store/chart/chart.slice.ts index f5ea8a86f86004df0ef552754d4c617c3fe90b13..60f4790d601c1edbfcf6bf9b35e61af23320d940 100644 --- a/src/store/chart/chart.slice.ts +++ b/src/store/chart/chart.slice.ts @@ -3,25 +3,6 @@ import { TimeStep } from 'enums' import { DateTime } from 'luxon' import { ChartState, Datachart } from 'models' -type SetCurrentDataChart = PayloadAction<Datachart> -type SetCurrentDataChartIndex = PayloadAction<number> -type SetCurrentIndex = PayloadAction<number> -type SetCurrentTimeStep = PayloadAction<TimeStep> -type SetLoading = PayloadAction<boolean> -type SetSelectedDate = PayloadAction<DateTime> -type SetShowCompare = PayloadAction<boolean> -type SetShowOfflineData = PayloadAction<boolean> - -export type ChartActionTypes = - | SetCurrentDataChart - | SetCurrentDataChartIndex - | SetCurrentIndex - | SetCurrentTimeStep - | SetLoading - | SetSelectedDate - | SetShowCompare - | SetShowOfflineData - const initialState: ChartState = { selectedDate: DateTime.local().endOf('minute').setZone('utc', { keepLocalTime: true, @@ -39,31 +20,31 @@ export const chartSlice = createSlice({ name: 'chart', initialState, reducers: { - setCurrentDataChart: (state, action: SetCurrentDataChart) => { + setCurrentDataChart: (state, action: PayloadAction<Datachart>) => { state.currentDatachart = action.payload }, - setCurrentDataChartIndex: (state, action: SetCurrentDataChartIndex) => { + setCurrentDataChartIndex: (state, action: PayloadAction<number>) => { state.currentDatachartIndex = action.payload }, - setCurrentIndex: (state, action: SetCurrentIndex) => { + setCurrentIndex: (state, action: PayloadAction<number>) => { state.currentIndex = action.payload }, - setCurrentTimeStep: (state, action: SetCurrentTimeStep) => { + setCurrentTimeStep: (state, action: PayloadAction<TimeStep>) => { state.currentTimeStep = action.payload if (state.currentTimeStep === TimeStep.YEAR) { state.showCompare = false } }, - setLoading: (state, action: SetLoading) => { + setLoading: (state, action: PayloadAction<boolean>) => { state.loading = action.payload }, - setSelectedDate: (state, action: SetSelectedDate) => { + setSelectedDate: (state, action: PayloadAction<DateTime>) => { state.selectedDate = action.payload }, - setShowCompare: (state, action: SetShowCompare) => { + setShowCompare: (state, action: PayloadAction<boolean>) => { state.showCompare = action.payload }, - setShowOfflineData: (state, action: SetShowOfflineData) => { + setShowOfflineData: (state, action: PayloadAction<boolean>) => { state.showOfflineData = action.payload }, }, diff --git a/src/store/global/global.slice.spec.ts b/src/store/global/global.slice.spec.ts index da398b617868cc5bd172a0604105498a3be6eaee..b4fc3d507a4612d127f3e2196de7e5012c5bdd2d 100644 --- a/src/store/global/global.slice.spec.ts +++ b/src/store/global/global.slice.spec.ts @@ -301,13 +301,11 @@ describe('globalSlice', () => { it('should handle setLastEpglLogin', () => { const state = globalSlice.reducer( mockGlobalState, - setLastEpglLogin({ - lastEpglLogin: 'username', - }) + setLastEpglLogin('1234567') ) expect(state).toEqual({ ...mockGlobalState, - lastEpglLogin: 'username', + lastEpglLogin: '1234567', }) }) diff --git a/src/store/global/global.slice.ts b/src/store/global/global.slice.ts index 4a9ed0a1609ec3e4bbce6a6999336fb3e0744225..f251ba453ab9d70ae6008aa7156ba6695c49aa98 100644 --- a/src/store/global/global.slice.ts +++ b/src/store/global/global.slice.ts @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import { PayloadAction, createSlice } from '@reduxjs/toolkit' import { FluidSlugType, FluidState, FluidType, ScreenType, Usage } from 'enums' import { @@ -121,45 +122,15 @@ const initialState: GlobalState = { lastEpglLogin: '', } -type ChangeScreenType = PayloadAction<ScreenType> -type SetPartnersInfo = PayloadAction<PartnersInfo> -type ToggleChallengeExplorationNotification = PayloadAction<boolean> -type ToggleChallengeActionNotification = PayloadAction<boolean> -type ToggleChallengeDuelNotification = PayloadAction<boolean> -type ToggleAnalysisNotification = PayloadAction<boolean> -type SetFluidStatus = PayloadAction<FluidStatus[]> type UpdatedFluidConnection = PayloadAction<{ fluidType: FluidType fluidConnection: FluidConnection }> -type SetLastEpglLogin = PayloadAction<{ - lastEpglLogin: string -}> -type UpdateTermValidation = PayloadAction<TermsStatus> type ShowReleaseNote = PayloadAction<{ show: boolean notes: Notes[] redirectLink?: string }> -type SetShouldRefreshConsent = PayloadAction<boolean> -type SetSgeConnect = PayloadAction<SgeStore> -type UpdateEcogestureFilter = PayloadAction<Usage> - -export type GlobalActionTypes = - | ChangeScreenType - | SetFluidStatus - | SetLastEpglLogin - | SetPartnersInfo - | SetSgeConnect - | SetShouldRefreshConsent - | ShowReleaseNote - | ToggleAnalysisNotification - | ToggleChallengeActionNotification - | ToggleChallengeDuelNotification - | ToggleChallengeExplorationNotification - | UpdatedFluidConnection - | UpdateEcogestureFilter - | UpdateTermValidation const getFluidTypesFromStatus = (fluidStatus: FluidStatus[]): FluidType[] => { const fluidTypes: FluidType[] = [] @@ -182,45 +153,45 @@ export const globalSlice = createSlice({ name: 'global', initialState, reducers: { - changeScreenType: (state, action: ChangeScreenType) => { + changeScreenType: (state, action: PayloadAction<ScreenType>) => { state.screenType = action.payload }, toggleChallengeExplorationNotification: ( state, - action: ToggleChallengeExplorationNotification + action: PayloadAction<boolean> ) => { state.challengeExplorationNotification = action.payload }, toggleChallengeActionNotification: ( state, - action: ToggleChallengeActionNotification + action: PayloadAction<boolean> ) => { state.challengeActionNotification = action.payload }, toggleChallengeDuelNotification: ( state, - action: ToggleChallengeDuelNotification + action: PayloadAction<boolean> ) => { state.challengeDuelNotification = action.payload }, - toggleAnalysisNotification: (state, action: ToggleAnalysisNotification) => { + toggleAnalysisNotification: (state, action: PayloadAction<boolean>) => { state.analysisNotification = action.payload }, - setFluidStatus: (state, action: SetFluidStatus) => { + setFluidStatus: (state, action: PayloadAction<FluidStatus[]>) => { state.fluidStatus = action.payload state.fluidTypes = getFluidTypesFromStatus(action.payload) }, - updateTermsStatus: (state, action: UpdateTermValidation) => { + updateTermsStatus: (state, action: PayloadAction<TermsStatus>) => { state.termsStatus = action.payload }, showReleaseNotes: (state, action: ShowReleaseNote) => { state.releaseNotes = action.payload }, - setPartnersInfo: (state, action: SetPartnersInfo) => { + setPartnersInfo: (state, action: PayloadAction<PartnersInfo>) => { state.partnersInfo = action.payload }, - setShouldRefreshConsent: (state, action: SetShouldRefreshConsent) => { + setShouldRefreshConsent: (state, action: PayloadAction<boolean>) => { state.shouldRefreshConsent = action.payload }, updateFluidConnection: ( @@ -229,16 +200,13 @@ export const globalSlice = createSlice({ ) => { state.fluidStatus[fluidType].connection = fluidConnection }, - setLastEpglLogin: ( - state, - { payload: { lastEpglLogin } }: SetLastEpglLogin - ) => { - state.lastEpglLogin = lastEpglLogin + setLastEpglLogin: (state, action: PayloadAction<string>) => { + state.lastEpglLogin = action.payload }, - updateSgeStore: (state, action: SetSgeConnect) => { + updateSgeStore: (state, action: PayloadAction<SgeStore>) => { state.sgeConnect = action.payload }, - updateEcogestureFilter: (state, action: UpdateEcogestureFilter) => { + updateEcogestureFilter: (state, action: PayloadAction<Usage>) => { state.ecogestureFilter = action.payload }, }, diff --git a/src/store/hooks.ts b/src/store/hooks.ts index 10ca3366a922c0e1e24ca26ab9df7e9184656b6b..21a319209c0bc26aa1d502d3593373536345c336 100644 --- a/src/store/hooks.ts +++ b/src/store/hooks.ts @@ -1,16 +1,9 @@ -import { Client } from 'cozy-client' +// eslint-disable-next-line @typescript-eslint/no-restricted-imports import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' -import { ThunkDispatch } from 'redux-thunk' -import { AppActionsTypes, AppStore } from './store' +import { AppDispatch, AppState } from './store' + // Typed hooks // https://redux.js.org/tutorials/typescript-quick-start#define-typed-hooks -/** Combine classic actions and thunk actions */ -type TypedDispatch = ThunkDispatch< - AppStore, - { client: Client }, - AppActionsTypes -> -export const useAppDispatch = () => useDispatch<TypedDispatch>() -// TODO maybe use AppEcolyoStore -export const useAppSelector: TypedUseSelectorHook<AppStore> = useSelector +export const useAppDispatch: () => AppDispatch = useDispatch +export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector diff --git a/src/store/modal/modal.slice.ts b/src/store/modal/modal.slice.ts index 8f4afd48cf616ef370e1bfc7f235bef4a666aca2..c65edeeca335afd67272d43e1cc2fa14b99caa76 100644 --- a/src/store/modal/modal.slice.ts +++ b/src/store/modal/modal.slice.ts @@ -17,34 +17,26 @@ const initialState: ModalState = { }, } -type OpenFeedbackModalAction = PayloadAction<boolean> -type OpenConnectionModalAction = PayloadAction<boolean> type OpenPartnersModalAction = PayloadAction<{ egl: boolean enedis: boolean grdf: boolean }> -type SetCustomPopup = PayloadAction<CustomPopup> - -export type ModalActionTypes = - | OpenFeedbackModalAction - | OpenPartnersModalAction - | SetCustomPopup export const modalSlice = createSlice({ name: 'modal', initialState, reducers: { - openFeedbackModal: (state, action: OpenFeedbackModalAction) => { + openFeedbackModal: (state, action: PayloadAction<boolean>) => { state.isFeedbacksOpen = action.payload }, openPartnersModal: (state, action: OpenPartnersModalAction) => { state.partnersIssueModal = action.payload }, - openConnectionModal: (state, action: OpenConnectionModalAction) => { + openConnectionModal: (state, action: PayloadAction<boolean>) => { state.isConnectionModalOpen = action.payload }, - setCustomPopup: (state, action: SetCustomPopup) => { + setCustomPopup: (state, action: PayloadAction<CustomPopup>) => { state.customPopupModal = action.payload }, }, diff --git a/src/store/profile/profile.slice.ts b/src/store/profile/profile.slice.ts index 1cc32cf3328353e6091a4096f4bdfeb6373989c8..c2d5c3a5c3539b51bef2219bbfeda08974125b1c 100644 --- a/src/store/profile/profile.slice.ts +++ b/src/store/profile/profile.slice.ts @@ -3,7 +3,7 @@ import { Client } from 'cozy-client' import { DateTime } from 'luxon' import { Profile } from 'models' import ProfileService from 'services/profile.service' -import { AppDispatch, AppStore } from 'store/store' +import { AppDispatch, AppState } from 'store/store' const initialState: Profile = { id: '', @@ -67,7 +67,7 @@ export const profileSlice = createSlice({ export const updateProfile = createAsyncThunk< Profile | void, Partial<Profile>, - { dispatch: AppDispatch; state: AppStore; extra: { client: Client } } + { dispatch: AppDispatch; state: AppState; extra: { client: Client } } >('profile/updateProfile', async (profileUpdates, thunkAPI) => { const client = thunkAPI.extra.client const profileService = new ProfileService(client) diff --git a/src/store/profileEcogesture/profileEcogesture.slice.ts b/src/store/profileEcogesture/profileEcogesture.slice.ts index 70fbc788b6b3900b96c88879826716d9e03e5fbf..73b40b443406955ae193c134b02c9a29e1be4a03 100644 --- a/src/store/profileEcogesture/profileEcogesture.slice.ts +++ b/src/store/profileEcogesture/profileEcogesture.slice.ts @@ -3,7 +3,7 @@ import { Client } from 'cozy-client' import { PROFILEECOGESTURE_DOCTYPE } from 'doctypes' import { IndividualOrCollective, WarmingType } from 'enums' import { ProfileEcogesture } from 'models' -import { AppDispatch, AppStore } from 'store/store' +import { AppDispatch, AppState } from 'store/store' const initialState: ProfileEcogesture = { heating: IndividualOrCollective.INDIVIDUAL, @@ -12,15 +12,11 @@ const initialState: ProfileEcogesture = { equipments: [], } -type UpdateProfileEcogesture = PayloadAction<ProfileEcogesture> - -export type ProfileEcogestureActionTypes = UpdateProfileEcogesture - export const profileEcogestureSlice = createSlice({ name: 'profileEcogesture', initialState, reducers: { - setProfileEcogesture: (state, action: UpdateProfileEcogesture) => { + setProfileEcogesture: (state, action: PayloadAction<ProfileEcogesture>) => { Object.assign(state, action.payload) }, }, @@ -40,7 +36,7 @@ export const { setProfileEcogesture } = profileEcogestureSlice.actions export const newProfileEcogestureEntry = createAsyncThunk< ProfileEcogesture | void, Partial<ProfileEcogesture>, - { dispatch: AppDispatch; state: AppStore; extra: { client: Client } } + { dispatch: AppDispatch; state: AppState; extra: { client: Client } } >('profileEcogesture/newProfileEcogesture', async (updates, thunkAPI) => { const client = thunkAPI.extra.client const { data: newProfileEcogesture } = await client.create( diff --git a/src/store/profileType/profileType.slice.ts b/src/store/profileType/profileType.slice.ts index 861804a5ea86991177d8aea4b9a7fa57b2c22bb6..1a6cdd51610ee997aea857f2558264575e5cd71d 100644 --- a/src/store/profileType/profileType.slice.ts +++ b/src/store/profileType/profileType.slice.ts @@ -39,15 +39,11 @@ const initialState: ProfileType = { equipments: [], } -type SetProfileType = PayloadAction<Partial<ProfileType>> - -export type ProfileTypeActionTypes = SetProfileType - export const profileTypeSlice = createSlice({ name: 'profileType', initialState, reducers: { - setProfileType: (state, action: SetProfileType) => { + setProfileType: (state, action: PayloadAction<Partial<ProfileType>>) => { Object.assign(state, action.payload) }, }, diff --git a/src/store/store.ts b/src/store/store.ts index 451808836b482523ec9819d76e5320a979975b2e..7c5e74fd41d4fe3f4e40526a4b075785f5ebf160 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,3 +1,4 @@ +import { configureStore } from '@reduxjs/toolkit' import * as Sentry from '@sentry/react' import { Client } from 'cozy-client' import { @@ -10,36 +11,28 @@ import { ProfileEcogesture, ProfileType, } from 'models' -import { applyMiddleware, combineReducers, compose, createStore } from 'redux' -import { composeWithDevTools } from 'redux-devtools-extension' -import thunkMiddleware from 'redux-thunk' -import { AnalysisActionTypes, analysisSlice } from './analysis/analysis.slice' -import { - ChallengeActionTypes, - challengeSlice, -} from './challenge/challenge.slice' -import { ChartActionTypes, chartSlice } from './chart/chart.slice' -import { GlobalActionTypes, globalSlice } from './global/global.slice' -import { ModalActionTypes, modalSlice } from './modal/modal.slice' +import { combineReducers } from 'redux' +import { analysisSlice } from './analysis/analysis.slice' +import { challengeSlice } from './challenge/challenge.slice' +import { chartSlice } from './chart/chart.slice' +import { globalSlice } from './global/global.slice' +import { modalSlice } from './modal/modal.slice' import { profileSlice } from './profile/profile.slice' -import { - ProfileEcogestureActionTypes, - profileEcogestureSlice, -} from './profileEcogesture/profileEcogesture.slice' -import { - ProfileTypeActionTypes, - profileTypeSlice, -} from './profileType/profileType.slice' +import { profileEcogestureSlice } from './profileEcogesture/profileEcogesture.slice' +import { profileTypeSlice } from './profileType/profileType.slice' -export interface EcolyoState { - analysis: AnalysisState - challenge: ChallengeState - chart: ChartState - global: GlobalState - modal: ModalState - profile: Profile - profileEcogesture: ProfileEcogesture - profileType: ProfileType +export interface AppState { + cozy: unknown + ecolyo: { + analysis: AnalysisState + challenge: ChallengeState + chart: ChartState + global: GlobalState + modal: ModalState + profile: Profile + profileEcogesture: ProfileEcogesture + profileType: ProfileType + } } /** Partial interfaces used for testing purposes */ @@ -54,9 +47,9 @@ export interface MockEcolyoState { profileType: Partial<ProfileType> } -export const defaultAction = { type: null, payload: undefined } +const sentryReduxEnhancer = Sentry.createReduxEnhancer({}) -const ecolyoReducer = combineReducers({ +const ecolyo = combineReducers({ analysis: analysisSlice.reducer, challenge: challengeSlice.reducer, chart: chartSlice.reducer, @@ -67,54 +60,23 @@ const ecolyoReducer = combineReducers({ profileType: profileTypeSlice.reducer, }) -export interface AppStore { - ecolyo: EcolyoState - cozy: unknown -} - -const appActions = { - ...analysisSlice.actions, - ...challengeSlice.actions, - ...chartSlice.actions, - ...globalSlice.actions, - ...modalSlice.actions, - ...profileEcogestureSlice.actions, - ...profileSlice.actions, - ...profileTypeSlice.actions, -} - -// TODO refactor types with AppActionsTypes = typeof appActions -export type AppActionsTypes = - | AnalysisActionTypes - | ChallengeActionTypes - | ChartActionTypes - | GlobalActionTypes - | ModalActionTypes - | ProfileEcogestureActionTypes - | ProfileTypeActionTypes - -const sentryReduxEnhancer = Sentry.createReduxEnhancer({}) - -const configureStore = (client: Client, persistedState: any) => { - const composeEnhancers = - composeWithDevTools({ trace: true, actionCreators: appActions }) || compose - - const store = createStore( - combineReducers({ - ecolyo: ecolyoReducer, +/** setupStore function to configure redux store taking an extra argument 'client' */ +export const setupStore = (client: Client) => { + const store = configureStore({ + reducer: { cozy: client.reducer(), - persistedState, - }), - composeEnhancers( - applyMiddleware(thunkMiddleware.withExtraArgument({ client })), - sentryReduxEnhancer - ) - ) + ecolyo, + }, + middleware: getDefaultMiddleware => + getDefaultMiddleware({ + serializableCheck: false, + thunk: { extraArgument: { client } }, + }), + devTools: true, + enhancers: [sentryReduxEnhancer], + }) + return store } -export default configureStore -// Infer the `RootState` and `AppDispatch` types from the store itself -export type AppEcolyoState = ReturnType<typeof ecolyoReducer> // TODO should use "ReturnType<typeof store.getState>" when using configureStore -export type AppStores = ReturnType<typeof configureStore> -export type AppDispatch = AppStores['dispatch'] +export type AppDispatch = ReturnType<typeof setupStore>['dispatch'] diff --git a/src/targets/browser/index.tsx b/src/targets/browser/index.tsx index 54ba4fbb6d4764574a411db77efb2f4d79adfc06..353a7bffaa17059c25cf619ee6edfb78747d4ad6 100644 --- a/src/targets/browser/index.tsx +++ b/src/targets/browser/index.tsx @@ -20,7 +20,7 @@ import { render } from 'react-dom' import { Provider } from 'react-redux' import { HashRouter } from 'react-router-dom' import EnvironmentService from 'services/environment.service' -import configureStore from 'store/store' +import { setupStore } from 'store/store' import cozyBar from 'utils/cozyBar' import logApp from 'utils/logger' import MatomoTracker from 'utils/matomoTracker' @@ -50,8 +50,7 @@ const setupApp = memoize(() => { schema, }) - const persistedState: any = {} - const store = configureStore(client, persistedState) + const store = setupStore(client) const envService = new EnvironmentService() const isLocal = envService.isLocal() const development = envService.isDev() diff --git a/tests/__mocks__/store/index.ts b/tests/__mocks__/store/index.ts index 96f96fb686faa571f3a6bbc53391b58f80979ea5..1a4f61367a19175eb437a64c63a5ec6907442f45 100644 --- a/tests/__mocks__/store/index.ts +++ b/tests/__mocks__/store/index.ts @@ -5,4 +5,4 @@ export * from './global.state.mock' export * from './modal.state.mock' export * from './profile.state.mock' export * from './profileType.state.mock' -export * from './store' +export * from './store.mock' diff --git a/tests/__mocks__/store/store.ts b/tests/__mocks__/store/store.mock.ts similarity index 88% rename from tests/__mocks__/store/store.ts rename to tests/__mocks__/store/store.mock.ts index 13a90c7ef181ffc8ebbd6b8d2065a3602452cff8..cc4be7aa4f975f89994702509d0c7fa4095769d7 100644 --- a/tests/__mocks__/store/store.ts +++ b/tests/__mocks__/store/store.mock.ts @@ -1,6 +1,7 @@ +import { AnyAction } from '@reduxjs/toolkit' import configureStore from 'redux-mock-store' import thunkMiddleware from 'redux-thunk' -import { AppActionsTypes, EcolyoState, MockEcolyoState } from 'store/store' +import { AppState, MockEcolyoState } from 'store/store' import mockClient from '../client.mock' import { mockProfileEcogesture } from '../profileEcogesture.mock' import { mockAnalysisState } from './analysis.state.mock' @@ -11,7 +12,7 @@ import { mockModalState } from './modal.state.mock' import { mockProfileState } from './profile.state.mock' import { mockProfileTypeState } from './profileType.state.mock' -export const mockInitialEcolyoState: EcolyoState = { +export const mockInitialEcolyoState: AppState['ecolyo'] = { analysis: mockAnalysisState, challenge: mockChallengeState, chart: mockChartState, @@ -25,7 +26,7 @@ export const mockInitialEcolyoState: EcolyoState = { const middlewares = [thunkMiddleware.withExtraArgument({ mockClient })] const mockStore = configureStore< { ecolyo: Partial<MockEcolyoState>; cozy: unknown }, - AppActionsTypes + AnyAction >(middlewares) /** diff --git a/yarn.lock b/yarn.lock index b55ea6eebcd092c6f8474048c41ff059ca6201bb..7bf41b3b3e8668c496c441abade5462171a6946b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1473,6 +1473,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.1": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" + integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/runtime@^7.16.3": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" @@ -2338,15 +2345,15 @@ "@react-spring/core" "9.0.0-rc.3" "@react-spring/shared" "9.0.0-rc.3" -"@reduxjs/toolkit@^1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.3.tgz#27e1a33072b5a312e4f7fa19247fec160bbb2df9" - integrity sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg== +"@reduxjs/toolkit@^1.9.5": + version "1.9.5" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4" + integrity sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ== dependencies: - immer "^9.0.16" - redux "^4.2.0" + immer "^9.0.21" + redux "^4.2.1" redux-thunk "^2.4.2" - reselect "^4.1.7" + reselect "^4.1.8" "@remix-run/router@1.2.1": version "1.2.1" @@ -2828,7 +2835,7 @@ dependencies: history "*" -"@types/hoist-non-react-statics@^3.3.0": +"@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== @@ -2961,7 +2968,7 @@ dependencies: "@types/react" "*" -"@types/react-redux@^7.1.11", "@types/react-redux@^7.1.20": +"@types/react-redux@^7.1.20": version "7.1.24" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0" integrity sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ== @@ -3040,6 +3047,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -9440,7 +9452,7 @@ immediate@3.0.6: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -immer@^9.0.16: +immer@^9.0.21: version "9.0.21" resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== @@ -14094,7 +14106,7 @@ react-redux@5.1.1: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" -react-redux@^7.2.0, react-redux@^7.2.2: +react-redux@^7.2.0: version "7.2.8" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de" integrity sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw== @@ -14106,6 +14118,18 @@ react-redux@^7.2.0, react-redux@^7.2.2: prop-types "^15.7.2" react-is "^17.0.2" +react-redux@^8.1.2: + version "8.1.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188" + integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw== + dependencies: + "@babel/runtime" "^7.12.1" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/use-sync-external-store" "^0.0.3" + hoist-non-react-statics "^3.3.2" + react-is "^18.0.0" + use-sync-external-store "^1.0.0" + react-remove-scroll-bar@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.3.tgz#e291f71b1bb30f5f67f023765b7435f4b2b2cd94" @@ -14449,7 +14473,7 @@ redux@4.1.2: dependencies: "@babel/runtime" "^7.9.2" -redux@^4.2.0: +redux@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== @@ -14693,10 +14717,10 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -reselect@^4.1.7: - version "4.1.7" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42" - integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== +reselect@^4.1.8: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== resolve-cwd@^2.0.0: version "2.0.0" @@ -16778,6 +16802,11 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" +use-sync-external-store@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"